盤面を 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 は変えていない¶
列も --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 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 つ揃ったときだけ。
- サーバごとに、その会話の 最新のログファイルを選ぶ(名前が ISO なので辞書順=時刻順)
- その
sessionIdが、いま見ているセッションのものと一致する - 最後の行が
"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 を超えることがある。末尾から広げていく形にしてある。
- 末尾 256KB を読む
- 当たらなければ末尾 4MB を読む
- それでも当たらなければ全部読む
末尾だけを決め打ちで読むと届かない ── ツール結果が続く会話では、末尾 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。 セッションが動いていても、会話ログに書かれる までは進まない