コンテンツにスキップ

盤面を 1 コマンドで取る(ccs ls -l)

ccs ls -l は、立っているセッションの一覧に 「直近の依頼」「RSS」「最終更新」 を足して出す。

$ ccs ls -l
SLUG                    STATUS       RSS    AGE  REQUEST
app-dev-course-1        idle        153M    18m  ジブリのアプリも todo アプリも、そのテンプレートとして…
brand-assets            waiting     126M    16h  アクセス URL とアクセス後にやることをマニュアルとして…
ccs@ls-board            busy        364M     0m  ccs ls を拡張して、hub が盤面を低コストで取り直せるように…
hub                     idle        161M    21m  いま何のセッションが何をしているかを 1 画面で出して
tmp-3                   waiting     128M    16h  ページについて、プレイ・ルール・ご案内の 3 つに分けて…

何のためにあるか

ハブが「答える前に盤面を取り直す」ための入口。 取らずに答えると、同じことをしているセッションが既にあるのに 2 本目を立てる。 1 コマンドで全セッションの状況が 1 画面に出ることが要件なので、 ccs ls の結果を材料に別のスクリプトを噛ませる形は採らない。

列 何を見ているか
REQUEST 会話ログの最後の user メッセージ。ツール結果と定型文は除く
RSS その claude プロセスの実メモリ(MB)。畳むかどうかの判断材料
AGE 最後に人が打った依頼の時刻からの経過(3m / 2h / 5d)。時刻が取れなければ会話ログの mtime

STATUS はレジストリの値(idle / busy / waiting)だが、アプリで閉じられた セッションは archived / deleted と出す(下記)。

-l は --long とも書ける。--json と併せると、既定のキー (slug / status / sessionId / path / tmux)を残したまま pid / rssMb / updatedAt / age / request / closed / requestedAt が足される。

AGE は mtime ではない

会話ログの mtime は「最後に生きていた時刻」であって「最後に触った時刻」ではない。 hub からのメッセージ(棚卸し)が届くたびに、シャットダウンで書き切られるたびに進む ── 実測(2026-09-11)では 30 本のうち 12 本が、3 日触っていないのに「今日動いた」顔を していた。放置を放置として見せるため、AGE は REQUEST と同じ行の timestamp から数える(requestedAt)。updatedAt(mtime)は残してある ── restore --last が見るのは こちらで、あれは「一緒に落ちた」を mtime の塊で切るので、意図して触っていない。

$ ccs ls -l --json | jq -r '.[] | "\(.slug)\t\(.rssMb)M\t\(.request)"'

既定の ccs ls は変えていない

列も --json のキーも増やさない。 盤面の列は会話ログを舐める分だけ確実に 遅くなるので、「立っているものを知りたいだけ」の用途に払わせない。 ccs ls を読んでいる既存の呼び出しは、そのまま動く。

ccs ls -l の表は SESSION ID と PATH を出さない。13 本を 1 画面に 収めるのが目的なので、36 桁の uuid と長いパスを並べると依頼が折り返して 読めなくなる。uuid が要るときは既定の ccs ls、機械で読むなら ccs ls -l --json。

アプリで閉じられたセッション

claude.ai やスマホアプリの「アーカイブ」「削除」は、ローカルの claude に届く。 ただし届いた claude は Remote Control を切るだけで、プロセスは生き続ける (実測 2026-09-11)。tmux のペインは残り、レジストリは idle のまま、ccs ls にも 並ぶ ── 人はアプリで畳んだつもりなのに、誰にも畳まれない。

$ ccs ls -l
SLUG            STATUS       RSS    AGE  REQUEST
tmp-ba825913    archived    121M    29m  買い物リストの Photo を埋めて…
tmp-1ae1ffc0    deleted     131M    23h  allowlist に何を足した?

痕跡は 2 か所にある。両方揃ったときだけそう出す。

どこ 何
会話ログ system 行が 1 本書かれる。アーカイブは Remote Control disconnected — this session was ended or archived from another device or app (code 4090)、削除は … the server no longer reports this session — it may have been deleted …
レジストリ bridgeSessionId が null になる

片方では足りない。 一度アーカイブした会話を ccs restore で立て直すと Remote Control は同じ id で付き直り、会話ログには古い行が残ったまま普通のセッションに 戻る(レジストリを見ればそれが分かる)。逆に bridgeSessionId が null になる経路は他にも ある(同じ会話を 2 本開いたときの取り合い)。会話ログの行が無ければ、閉じたのではない。

さらに、disconnect の行より後に人が打った依頼があれば、閉じていないと読む。 ccs attach で乗り込んで続けたなら、それは生きているセッション。hub からの メッセージは「人が打った」に数えない(次節)。

畳むのは ccs gc の仕事 ── 「アプリで閉じられたセッション(畳む対象)」に 並び、--yes で畳む。畳んだ会話は「終わった」と記録され、再起動のあとの ccs restore に並ばない(名指しなら戻せる)。

--json では closed("archived" / "deleted" / null)が足される。status は 変えない ── レジストリの値をそのまま読んでいる側を壊さない。

MCP の健康診断(--mcp)

長く生きているセッションで MCP が落ちる。 実測(2026-09-03、8 日稼働のセッション)では 8 サーバが一斉に消え、そのうち claude.ai のコネクタ 5 つはエラーも出さずに再接続の試行ごと 止まっていた。/mcp reconnect all はあれらを管轄していないので戻せない。

$ ccs ls --mcp
SLUG              MCP  落ちているサーバ
app-dev-course-1  ok   -
tmp-7             NG   chatwork,freee-mcp,pencil

直し方は既存の道具。 ccs kill <slug> → ccs restore <slug> で、プロセスごと作り直せば 全部戻る。実測では会話も保たれ、OAuth の再認可も要らなかった。

  • ccs は検知しかしない。 自動では畳まない ── 作業中のセッションを黙って落とすほうが高くつく
  • -l とは併用できない。 あちらは「いま何をしているか」を見る盤面で、問いが違う
  • 落ちていないセッションも出す。 出ないと「調べたのか、調べていないのか」が区別できない

どう判定しているか

Claude Code が CCS_MCP_LOG_DIR の下に、接続 1 回 = 1 ファイルで JSONL を積む。 <エンコードした cwd>/mcp-logs-<server>/<ISO>.jsonl で、中に sessionId が入っている。

判定は 3 つ揃ったときだけ。

  1. サーバごとに、その会話の 最新のログファイルを選ぶ(名前が ISO なので辞書順=時刻順)
  2. その sessionId が、いま見ているセッションのものと一致する
  3. 最後の行が "error" を持つ

2 が要る。 同じ場所で会話を何度も立てていると古い失敗ログが残るので、素直に読むと 死んでいないものを死んだと言う。逆に、単に最新のファイルを取ってから会話を照合すると 取りこぼす ── 同じ場所に後から別の会話が立っただけで ok に見える(実測で踏んだ)。

見えないもの

  • claude.ai のコネクタが「黙って消える」のは検知できない。 エラーを吐かずに試行が止まるだけで、 ログには何も残らない。他のサーバより試行回数が少ないことでしか分からない
  • その会話の接続試行が記録されていなければ ok と出る。 落ちていても記録が無ければ見えない。 迷ったら安全側(偽の NG は健全なセッションを畳ませるので、そちらのほうが害が大きい)

ツール定義も古いまま固まる

検知の対象外だが、同じ実測で分かったこと。8 日動いていたセッションの Notion には、 以前は無かった 13 個のツールが載っていなかった。 再接続でサーバ側の最新の一覧を取り直すため。

接続が生きていても起きるし、エラーも出ない(「そのツールは存在しない」としか見えない)。 サーバが今いくつ持っているかを外から知る手段が無いので、検知のしようがない。 長寿命セッションは、落ちていなくても定期的に立て直したほうがよい。

「直近の依頼」の決め方

会話ログ(~/.claude/projects/**/<uuid>.jsonl)の type":"user" の行は、 実測した限り 3 通りしかない。

形 中身
{"role":"user","content":"…"} 人が打った依頼
{"role":"user","content":[{"type":"text",…}]} 人が打った依頼(添付つきなど)
{"role":"user","content":[{"tool_use_id":…}]} / [{"type":"tool_result",…}] ツール結果

前 2 つだけを拾えば、ツール結果は 1 件も混ざらない。そのうえで、 人が打っていない定型文は候補から外す。

  • [Request interrupted by user](中断の印)
  • <command-name>… <local-command-caveat>… <system-reminder>…(< で始まるもの)
  • [Image: …](画像だけを貼ったときの表示)
  • Caveat: The messages below were generated by …
  • Base directory for this skill: …(スキルの注入)
  • This session is being continued from … / Continue from where you left off / Please continue(継続プロンプト)

見つからなければ 1 つ前へ遡る。最大 12 本まで見て、それでも無ければさらに 広い範囲を読み直す(次節)。

表示は 70 桁で切る。文字数ではなく桁数で、全角は 2 桁として数える ── 文字数で切ると日本語の依頼が横 140 桁になり、1 画面に収まらなくなる。 切ったときは末尾に … が付く。

速さのために決めたこと

会話ログは 50MB を超えることがある。末尾から広げていく形にしてある。

  1. 末尾 256KB を読む
  2. 当たらなければ末尾 4MB を読む
  3. それでも当たらなければ全部読む

末尾だけを決め打ちで読むと届かない ── ツール結果が続く会話では、末尾 300KB を 読んでも user 行に届かず「直近の依頼」が空欄になる。かといって毎回全部読むと 遅い。実測(macOS 15 / BSD grep / 50MB の会話ログ / ページキャッシュ温):

読み方 時間
末尾 256KB 0.01 秒
末尾 4MB 0.12 秒
全部(50MB) 0.30 秒

grep の速さを測るときは、対話シェルの別名に注意すること。 ここは一度取り違えた ── 手元の zsh では grep が ugrep に化けていて、 同じ 50MB が 0.019 秒で終わる。ccs が起動するのは /usr/bin/grep なので、 その数字で設計すると 30 倍外す。

ほかにも、行ごとにプロセスを起こさないようにしてある。 ps は全 pid をまとめて 1 回、会話ログのパスは awk 1 回、 「いま」の時刻は 1 回だけ取る。

モデルは一切呼ばない。 依頼の文はテキスト処理でそのまま切り出しているだけで、 要約もしない。

hub からのメッセージ(Another Claude session sent a message:)も定型文として 落とす。 組み込みの SendMessage が届けるもので、人が打ったものではない。出すと、 放置しているセッションが hub の棚卸しのたびに「いま依頼された」顔になる。

実測(15 本)

コマンド 時間(5 回)
ccs ls 0.37〜0.43 秒
ccs ls -l 0.90〜0.96 秒

マシンの負荷で振れる。 同じ手元で 15 本の Claude が動いている時間帯には ccs ls -l が 1.2〜1.4 秒まで伸びた。

差の大半は「直近の依頼」を引く分。残りの 0.4 秒は既定の ccs ls が元から 持っているコスト(レジストリのファイルを slug ごとに舐める)で、盤面とは別の話。 ここを削るなら collect_session_row の側の仕事になる。

限界

  • チャンクの切れ目は行の途中から始まる。 半端な行は JSON として読めないので 黙って落ちる。切れ目がちょうど最後の依頼の行を割ったときだけ、1 つ前の依頼が 出る(2KB の行が 256KB の切れ目に当たる確率)
  • 画像だけを貼ったやり取りは「依頼」に数えない。 その場合はさらに前の テキストの依頼まで遡るので、その 1 本だけ全部読むことになる
  • AGE は会話ログの mtime。 セッションが動いていても、会話ログに書かれる までは進まない