claude attach/logs/stop/respawn/rm を実機で使い倒す:v2.1.251の「Added」は、実は手元の旧バージョンでもう動いていた

claude attach/logs/stop/respawn/rm を実機で使い倒す:v2.1.251の「Added」は、実は手元の旧バージョンでもう動いていた Claude Code活用

はじめに

YouTube「AI大学【AI&ChatGPT最新情報】」の直近動画一覧を確認すると、14時間前に公開された「【今週のAIツール・ニュース総まとめ】ChatGPTとCodex大量アプデ/ClaudeとClaude Code関連大量アプデ/M6・M5 Ultra搭載の新Mac miniとMac Studio」が、Claude Code関連の大量アップデートに触れていた。動画は他の話題も含む総まとめ動画で、内容の転載はせず、あくまで「直近でClaude Codeにまとまった新機能が出ている」という着想だけを借り、事実は公式のchangelog(code.claude.com/docs/en/changelog)とCLIリファレンス(code.claude.com/docs/en/cli-reference)、hooksドキュメント(code.claude.com/docs/en/hooks)で裏取りしている。

changelogのv2.1.251(8/28)を見ると、バックグラウンドセッション管理に関わる項目が複数入っていた。

  • claude --helpにattach・logs・stop・respawn・rmが追加された
  • --resumeのメッセージが、実行中のバックグラウンドセッションに対して正確なclaude attach <id>コマンドを表示するようになった
  • SessionStartのresumeフックが、セッションの「経過時間」と「再キャッシュの推定コスト」を受け取るようになった

前回(2026-08-30)の記事では、手元のv2.1.239が要求バージョンに届いていない新機能を検証し、「CLIフラグは即エラーで弾かれる」「設定ファイル経由の新フックは無言で無視される」という2つの壊れ方を確認した。今回も同じやり方で検証を始めたところ、予想が外れた。上記3つのうち2つが、要求バージョンに届いていないはずの手元のv2.1.239で、すでに普通に動いていた。バージョン番号だけを見て「まだ使えない」と判断するのは早計だった、という気づきが今回の核になる。

あわせて、claude --bgでバックグラウンドセッションを起動し、agents --jsonで一覧、logsでログ確認、stop→respawnで再起動、rmで片付けるところまで、ライフサイクル全体を実際に動かした。この一連の操作は「サブエージェント/バックグラウンドエージェントの実践」というテーマの中でもまだこのブログで扱っていない領域で、コマンド単体の説明だけでは分からない「生ログがそのままでは読めない」「rmの実際の挙動が--helpの説明と食い違って見える」といったつまずき所も見つかった。

手順: バックグラウンドセッションを起動→観察→片付けまで一気通貫で動かす

検証は/tmp/bgtestという使い捨てのGitリポジトリで行った。本物のプロジェクトや~/.claude/settings.jsonには一切変更を加えていない。

mkdir -p /tmp/bgtest && cd /tmp/bgtest
git init -q
echo "test file" > hello.txt
git add -A && git commit -q -m init

ステップ1: --bgでバックグラウンドセッションを起動する

claude --bg "Read hello.txt and tell me its contents. Do not modify any files."

実際の出力:

Starting background service…
backgrounded · fdf93167
  claude agents             list sessions
  claude attach fdf93167    open in this terminal
  claude logs fdf93167      show recent output
  claude stop fdf93167      stop this session

起動直後から、管理に使う4つのコマンド(agents/attach/logs/stop)がそのままヒントとして表示される。これは手元のv2.1.239でもすでに出ていた。

ステップ2: claude agents --jsonで状態を確認する

claude agents --json | python3 -c "
import json, sys
for a in json.load(sys.stdin):
    if a.get('id') == 'fdf93167':
        print(json.dumps(a, indent=2))
"

実際の出力:

{
  "pid": 5304,
  "id": "fdf93167",
  "cwd": "/private/tmp/bgtest",
  "kind": "background",
  "startedAt": 1788133533495,
  "sessionId": "fdf93167-2ca6-4eb3-889e-a0878227e39c",
  "name": "read hello.txt",
  "status": "idle",
  "state": "done"
}

claude agents --jsonには、このバックグラウンドセッション以外に、同じマシン上で動いている他の対話セッション(kind: "interactive")も一緒に列挙された。今回は自分の検証対象だけをidでフィルタしたが、実際の運用では--cwd <path>で対象ディレクトリを絞るか、jqで.kind == "background"を条件にすると見やすい。

ステップ3: claude logsで出力を見る — ここが最初のつまずき所

claude logs fdf93167 | cat -v | head -c 500

実際の出力(先頭抜粋、制御文字は^[等で可視化):

7[r8[?25h[?1049h[2J[H[?1000h[?1002h[?1003h[?1006h[?25l[?2004h[?1004h[?2031h]0;...

claude logs <id>は、バックグラウンドセッションが動いていた端末の生の画面出力(ANSIエスケープシーケンス込み)をそのまま返す。ドキュメントの説明(「最近の出力を表示する」)を読んだだけでは、この生々しさは伝わらない。実際に最終的な応答テキストを見たいだけなら、このコマンドの出力を目視で追うのは非効率で、agents --jsonが返すsessionIdから会話のtranscriptファイル(~/.claude/projects/<project>/<sessionId>.jsonl)を直接読む方が確実だった。

tail -c 2000 "/Users/$(whoami)/.claude/projects/-private-tmp-bgtest/fdf93167-2ca6-4eb3-889e-a0878227e39c.jsonl" | python3 -c "
import sys, json
for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    try:
        obj = json.loads(line)
    except json.JSONDecodeError:
        continue
    msg = obj.get('message', {})
    if msg.get('role') == 'assistant':
        for block in msg.get('content', []):
            if block.get('type') == 'text':
                print(block['text'])
"

実際の出力:

hello.txt contains a single line: test file

ステップ4: stop→respawnでライフサイクルを制御する

claude stop fdf93167
claude agents --json | grep -c '"id": "fdf93167"'
claude respawn fdf93167
sleep 3
claude agents --json | grep -A3 '"id": "fdf93167"'

実際の出力:

stopped fdf93167
0
respawned fdf93167
    "id": "fdf93167",
    "cwd": "/private/tmp/bgtest",
    "kind": "background",
    "startedAt": 1788133587424,

stop直後はagents --jsonの一覧からこのセッションが消える(会話自体は保持されている)。respawnすると新しいプロセスとして復活し、startedAtは更新されるがsessionIdとstate: "done"という以前の完了状態は保持されたまま戻ってくる。ドキュメント通り「会話は保持される」ことが実測でも確認できた。

ステップ5: rmで片付ける — ここが2つ目のつまずき所

claude rm fdf93167
ls -la /tmp/bgtest

実際の出力:

removed fdf93167
total 8
drwxr-xr-x@  4 harkingbee  wheel   128 Aug 31 08:45 .
drwxrwxrwt  85 root        wheel  2720 Aug 31 08:46 ..
drwxr-xr-x@ 12 harkingbee  wheel   384 Aug 31 08:45 .git
-rw-r--r--@  1 harkingbee  wheel    10 Aug 31 08:45 hello.txt

手元のclaude rm --helpは「Delete a background session and its worktree(バックグラウンドセッションとそのworktreeを削除する)」と説明している。これを読むと「rmするとディレクトリごと消えるのでは」と身構えるが、今回のように--worktreeを使わず既存ディレクトリで--bgしたケースでは、ディレクトリの中身(.gitとhello.txt)はそのまま残った。消えたのはセッション管理上の登録だけで、agents --json一覧からもこのIDは完全に消えた。公式のCLIリファレンスの説明(「リストから削除する。会話トランスクリプトはローカルに残りclaude --resumeで利用できる」)の方が今回の実測と一致する。--helpの「worktreeも削除する」という文言は、--worktreeで専用のGit worktreeを作って起動したセッションに限った話だと考えられる(この記事では--worktreeを使ったケース自体は検証していない)。

発見: v2.1.251の「Added」2件が、v2.1.239の手元環境でもう動いていた

ここまでは想定通りの検証だったが、残る2つの新機能を確認したところ、前回記事のパターンとは違う結果になった。

発見1: --resumeのメッセージに、すでにclaude attachの正確なコマンドが出ていた

cd /tmp/bgtest
claude --bg "Read hello.txt and tell me its contents."
# → backgrounded · <新しいID>、sessionIdをメモしておく
claude --resume <そのsessionId>

実際の出力:

Error: Session fdf93167-2ca6-4eb3-889e-a0878227e39c is running as a background session (fdf93167). Run `claude attach fdf93167` to open it, or `claude stop fdf93167` first to resume it here. Add --fork-session to branch off a copy instead.

changelogは「--resumeメッセージが正確なclaude attach <id>コマンドを表示するようになった」をv2.1.251の変更点として記載しているが、v2.1.239である手元の環境でも、まさにこの文言(claude attach fdf93167という実行可能なコマンド、stopとの使い分け、--fork-sessionの案内)がすでに出力されていた。

発見2: SessionStartのresumeフックに、すでにキャッシュ再生成コストの推定値が入っていた

対照実験と同じ要領で、resumeイベントの入力JSONをそのままファイルに書き出すフックを仕込んだ。

mkdir -p /tmp/bgtest/.claude
cat > /tmp/bgtest/.claude/settings.json << 'EOF'
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "resume",
        "hooks": [
          { "type": "command", "command": "cat > /tmp/bgtest_resume_hook_input.json" }
        ]
      }
    ]
  }
}
EOF
cd /tmp/bgtest
SID=$(claude -p "say hi in one word" --output-format json | python3 -c "import json,sys;print(json.load(sys.stdin)['session_id'])")
claude -p --resume "$SID" "say bye in one word" --output-format json > /dev/null
cat /tmp/bgtest_resume_hook_input.json

実際の出力:

{"session_id":"2681ef7c-946c-4e3b-ba3c-0eeaa0931f00","transcript_path":"/Users/harkingbee/.claude/projects/-private-tmp-bgtest/2681ef7c-946c-4e3b-ba3c-0eeaa0931f00.jsonl","cwd":"/private/tmp/bgtest","hook_event_name":"SessionStart","source":"resume","seconds_since_last_response":1,"context_tokens":37800,"prompt_cache_likely_expired":false,"estimated_cache_write_usd":0.1512}

seconds_since_last_response(前回応答からの経過秒数)、context_tokens(再開時点のコンテキストトークン数)、prompt_cache_likely_expired(プロンプトキャッシュが期限切れの可能性が高いか)、estimated_cache_write_usd(再キャッシュ発生時の推定コスト)という4フィールドは、changelogが言う「session staleness and the estimated re-cache cost(セッションの経過状況と再キャッシュの推定コスト)」に対応する内容そのものだった。しかも公式のhooksドキュメントの「共通の入力フィールド」一覧には、この4フィールドへの言及が見当たらない(session_id・hook_event_name・cwdなどの共通フィールドと、SessionStartだけがmodelフィールドを持ちうるという注記はあるが、この4つは個別に列挙されていない)。つまり、changelogに載って初めて存在を知ることができる、ドキュメント上は未文書化のフィールドだった。

この2つの発見から言えるのは、changelogの「Added」という表記は「このバージョンから初めて動くようになった」ことを必ずしも保証しない、ということだ。今回のケースでは、機能自体はそれ以前のバージョンからすでに存在していて、v2.1.251はそれを--helpやchangelogという「見える形」で正式に案内し始めた回だった可能性がある。前回記事で確立した「CLIフラグは即エラー・設定ファイル経由の新フックは無言で無視」という二極とは別に、「バージョン要件を満たしていなくても、実際に手を動かして試すと動くことがある」という3つ目のパターンが今回見つかったことになる。ただし、これが一般的な傾向なのか、今回のケース固有の事情なのかは、今回の1回の検証だけでは判断できない。

コピペ用プロンプト

1. バックグラウンドセッションを起動して管理コマンドを確認する

mkdir -p /tmp/claude-bg-demo && cd /tmp/claude-bg-demo && git init -q
echo "hello" > memo.txt && git add -A && git commit -q -m init
claude --bg "Read memo.txt and summarize its contents in one sentence. Do not modify any files."

期待できる結果: backgrounded · <8桁のID>という行と、claude agents/claude attach <id>/claude logs <id>/claude stop <id>の4行のヒントが表示される。
成功判定: 表示された<id>を控えておき、次のプロンプトのclaude agents --jsonの出力に同じIDが"kind": "background"として現れること。

2. 自分のバックグラウンドセッションだけを絞り込んで一覧する

claude agents --json | python3 -c "
import json, sys
data = json.load(sys.stdin)
bg = [a for a in data if a.get('kind') == 'background']
print(f'バックグラウンドセッション: {len(bg)}件')
for a in bg:
    print(f\"  {a.get('id')}: {a.get('name')} (status={a.get('status')}, state={a.get('state')})\")
"

期待できる結果: 手元で動いている・完了済みのバックグラウンドセッションの件数と、それぞれのid・タスク名・status(idle/busy等)・state(done等)が一覧表示される。
成功判定: 件数が0件でない、かつ直前に--bgで起動したセッションのIDが一覧に含まれていること。件数が想定より多い場合、過去にstopし忘れたセッションが残っている可能性があるので、次のプロンプトのrmで片付けるとよい。

3. claude logsではなくtranscriptファイルから最終応答だけを抜き出す

ID="ここに手順1で控えたセッションのIDを入れる"
SESSION_JSON=$(claude agents --json | python3 -c "
import json, sys
data = json.load(sys.stdin)
match = [a for a in data if a.get('id') == '$ID']
print(json.dumps(match[0]) if match else '')
")
if [ -n "$SESSION_JSON" ]; then
  SID=$(echo "$SESSION_JSON" | python3 -c "import json,sys;print(json.load(sys.stdin)['sessionId'])")
  CWD=$(echo "$SESSION_JSON" | python3 -c "import json,sys;print(json.load(sys.stdin)['cwd'])")
  PROJECT_DIR=$(echo "$CWD" | sed 's/\//-/g')
  TRANSCRIPT="$HOME/.claude/projects/${PROJECT_DIR}/${SID}.jsonl"
  python3 -c "
import json
with open('$TRANSCRIPT') as f:
    lines = [json.loads(l) for l in f if l.strip()]
for obj in reversed(lines):
    msg = obj.get('message', {})
    if msg.get('role') == 'assistant':
        for block in msg.get('content', []):
            if block.get('type') == 'text':
                print(block['text'])
        break
"
fi

(この例は穴埋めID=...を含むため、そのまま動く完結例は上の手順3節の実測コマンドを参照してほしい。ここでは手元の任意のセッションIDに差し替えて使う汎用テンプレートとして掲載している。)

期待できる結果: claude logsの生のANSI出力ではなく、そのバックグラウンドセッションの最後のアシスタント発言のテキストだけが表示される。
成功判定: 制御文字が混じらない、読める日本語または英語の文章が1つ出力されること。

4. stop→respawnで会話を保ったままプロセスだけ再起動する

ID="ここに手順1で控えたセッションのIDを入れる"
claude stop "$ID"
echo "停止後の一覧:"; claude agents --json | grep -c "\"id\": \"$ID\""
claude respawn "$ID"
sleep 3
echo "再起動後の一覧:"; claude agents --json | grep -A2 "\"id\": \"$ID\""

期待できる結果: stop直後はagents --jsonの一覧から件数が0になり、respawn後は同じid・同じsessionIdで再び一覧に現れる(startedAtだけ新しい時刻に更新される)。
成功判定: respawn後に表示されたstateが、停止前と同じ値(今回の検証ではdone)に戻っていること。バイナリを更新した後に既存のバックグラウンドセッションへ新しいバージョンを反映させたいときは、claude respawn --allで全セッションを一括再起動できる(--help記載、今回--allオプション自体は未実行)。

5. 使い終わったバックグラウンドセッションを片付ける

ID="ここに手順1で控えたセッションのIDを入れる"
claude rm "$ID"
claude agents --json | grep -c "\"id\": \"$ID\""

期待できる結果: removed <id>と表示され、agents --jsonの一覧からそのIDが完全に消える(件数0)。--worktreeを使わず既存ディレクトリで起動したセッションの場合、ディレクトリの中身は削除されない。
成功判定: grep -cの結果が0になること。あわせて、セッションを起動した作業ディレクトリのlsで、想定したファイルがすべて残っていることを目視確認する。

6. SessionStart(resume)のキャッシュ再生成コストを可視化する

mkdir -p /tmp/claude-cache-check/.claude && cd /tmp/claude-cache-check && git init -q
cat > .claude/settings.json << 'EOF'
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "resume",
        "hooks": [
          { "type": "command", "command": "cat > /tmp/claude-cache-check/resume_input.json" }
        ]
      }
    ]
  }
}
EOF
SID=$(claude -p "say hi in one word" --output-format json < /dev/null | python3 -c "import json,sys;print(json.load(sys.stdin)['session_id'])")
claude -p --resume "$SID" "say bye in one word" --output-format json < /dev/null > /dev/null
cat /tmp/claude-cache-check/resume_input.json | python3 -m json.tool

期待できる結果: seconds_since_last_response・context_tokens・prompt_cache_likely_expired・estimated_cache_write_usdを含むJSONが整形されて表示される。
成功判定: estimated_cache_write_usdが数値として出力されること。この値が大きいセッションほど、再開(resume)によってプロンプトキャッシュが作り直され、追加コストが発生する可能性が高いと読める。ただしこのフィールドの公式な定義・計算式はドキュメント化されていないため、目安として使い、正確な請求額は/costや実際の請求で確認すること。

活用例

  • 長時間かかる調査・リファクタリングを裏で走らせたい人: 手順1〜5のように、--bgで投げてagents --jsonで定期的に状態を確認し、終わったらrmで片付ける、というループを1つのシェルスクリプトにまとめておくと、複数の調査タスクを並行して回せる。放置しすぎたセッションが溜まっている場合は、プロンプト2の一覧スクリプトで棚卸しする。
  • バイナリを更新したのに古いバージョンで動き続けているバックグラウンドセッションが気になる人: claude respawn --allで既存の全バックグラウンドセッションを新しいバイナリに載せ替えられる(ドキュメント記載、今回未実行)。更新直後に一度実行しておくと、古いバイナリのまま動き続けるセッションを防げる。
  • チームでコスト管理をしている人: プロンプト6のフックを使うと、resumeのたびに再キャッシュの推定コストがログに残る。頻繁に--resumeされるセッション(例えば長時間放置されがちな調査タスク)でestimated_cache_write_usdが大きくなりがちなら、放置時間を減らす・こまめに/compactするといった対策の検討材料になる。

注意点

  • 今回の検証はすべて筆者の環境(v2.1.239、macOS)における実測であり、他のバージョンや他のOSで同じ挙動になるとは限らない。特に「発見1・発見2」で示した2つの機能がv2.1.239より前のどのバージョンから動いていたのかは特定していない。
  • claude rmが「ディレクトリごと消えるかどうか」は、今回検証した「--worktreeを使わず既存ディレクトリで--bgしたケース」に限った結果である。--worktreeで専用のワークツリーを作って起動したセッションをrmした場合の挙動は、この記事では検証していない。
  • SessionStart(resume)のcontext_tokens・prompt_cache_likely_expired・estimated_cache_write_usd・seconds_since_last_responseは、公式hooksドキュメントの入力フィールド一覧に明記が見当たらない未文書化のフィールドである。将来のバージョンでフィールド名や算出方法が変わる可能性があるので、フックの出力に依存した自動判断(自動でコストの高いセッションをkillするなど)は避け、あくまで人が確認する目安として使うのが安全。
  • 検証はすべて/tmp配下の使い捨てディレクトリで行い、本物のプロジェクトや~/.claude/settings.jsonには変更を加えていない。バックグラウンドセッションのIDは実行のたびに変わるため、この記事のコマンドをそのまま実行する場合は、自分の環境で表示された実際のIDに置き換える必要がある。

締め

claude attach/logs/stop/respawn/rmは、claude --helpに載ったのがv2.1.251というだけで、コマンド自体もその主要な出力フィールドも、手元のv2.1.239ですでに一通り動いた。今日からすぐ試せるのは、まず手順1〜5でバックグラウンドセッションを1つ起動し、agents --json→logs(または今回示したtranscript直読み)→stop→respawn→rmまで一気に動かしてみることだ。バージョン番号が新機能の要求ラインに届いていなくても、「まだ使えないはず」と決めつけずに一度手を動かして確認する価値があると分かったのが、今回の検証で得た一番の収穫だった。

Comments

Copied title and URL