Claude Media
CLAUDE_JOB_DIRでバックグラウンドセッションのスクラッチ領域を使う

CLAUDE_JOB_DIRでバックグラウンドセッションのスクラッチ領域を使う

バックグラウンドセッションに設定されるCLAUDE_JOB_DIRの中身と、$CLAUDE_JOB_DIR/tmpへの書き込みが権限プロンプトを出さない仕組みを解説します。

このTipsでできること

Claude Codeのバックグラウンドセッションには、CLAUDE_JOB_DIRという環境変数が設定されます。値は、そのセッション専用の~/.claude/jobs/<id>ディレクトリです。セッションが実行するシェルコマンドはこの変数を引き継ぐので、$CLAUDE_JOB_DIR/tmpを一時ファイルの置き場にすれば、並列で動くセッション同士で衝突しません。

Claude Codeのバックグラウンド実行は、claude --bgやagent viewから起動したセッションを、ターミナルを閉じても動かし続ける仕組みです。一覧取得やスクリプト連携はclaude agents --jsonでバックグラウンドセッションを操作するで扱いました。本稿は、そのセッションが使うディレクトリと環境変数に絞ります。

CLAUDE_JOB_DIRはどこを指しているか

agent viewからセッションをバックグラウンドで動かすと、セッションごとに~/.claude/jobs/<id>ができます。CLAUDE_JOB_DIRはこのパスをそのまま値に持つ環境変数です。<id>はclaude attach・claude logs・claude stop・claude rmが受け取る短いIDで、claude agents --jsonのidフィールドにも出ます。

実際のバックグラウンドセッション(v2.1.289)の中で値と中身を見ると、次のとおりでした。

echo "$CLAUDE_JOB_DIR"
# => ~/.claude/jobs/a59583ab
 
ls -A "$CLAUDE_JOB_DIR"
# => exit-cause
# => state.json
# => timeline.jsonl
# => tmp

tmpのほかに3つのファイルが並びます。このうち公式ドキュメントが用途を説明しているのはstate.jsonだけで、agent viewに表示するセッション状態を持つファイルです。直接パースせず、claude agents --json経由で読む前提になっています。

自分がバックグラウンドセッションの中にいるかどうかは、/statusのSession kind行で分かります。バックグラウンドならbackground job · attachedかbackground job · unattended、それ以外のセッションならinteractiveです。CLAUDE_JOB_DIRはバックグラウンドセッションに設定される変数として書かれているので、スクリプトの中では[ -n "$CLAUDE_JOB_DIR" ]で分岐する使い方になります。

$CLAUDE_JOB_DIR/tmpへのWrite / Editは確認なしで通る

一時ファイルの置き場所は$CLAUDE_JOB_DIR直下ではなく、その下のtmpサブディレクトリです。ClaudeのWriteとEditがここに書くときは、権限プロンプトが出ません。

ただし、ドキュメントが確認なしと書いているのはWriteとEditの呼び出しです。echo ... >> "$CLAUDE_JOB_DIR/tmp/x.log"のようにBashツール経由で書く場合の扱いは、記載がありません。プロンプトを避けたいなら、ファイルの作成と更新はこの2つに任せる形が確実です。

手順

並列セッションで一時ファイルを衝突させない手順

  1. 1

    書き込み先を決める

    プロジェクト直下ではなく、$CLAUDE_JOB_DIR/tmpを一時ファイルの置き場にします。セッションごとにパスが違うので、同じファイル名でも他のセッションとぶつかりません。

  2. 2

    スクリプトにパスを渡す

    シェルコマンドはCLAUDE_JOB_DIRを引き継ぎます。スクリプトの先頭でout="$CLAUDE_JOB_DIR/tmp"と受ければ、呼び出し側で引数を組み立てる必要がありません。

  3. 3

    成果物は別の場所へ出す

    tmpはセッションを削除すると消えます。残したい出力は、プロジェクトのworktree内に書いてコミットするか、別のパスへコピーします。

スクリプトの例を挙げます。テスト実行のログを、バックグラウンドセッションならtmpへ、それ以外なら共有の/tmpへ振り分けます。

if [ -n "$CLAUDE_JOB_DIR" ]; then
  log_dir="$CLAUDE_JOB_DIR/tmp"
else
  log_dir="/tmp/test-logs"
fi
mkdir -p "$log_dir"
npm test > "$log_dir/test.log" 2>&1

ログの置き場がセッションごとに決まるので、どのセッションのログかも迷いません。

2種類の衝突回避 — tmpとworktreeは役割が違う

並列セッションの衝突を避ける仕組みは2つあります。守る対象が違うので、混同しないことが大切です。

くらべる

スクラッチ領域とworktreeの住み分け

一時ファイル用

$CLAUDE_JOB_DIR/tmp

セッション固有のスクラッチ領域です。ここへのWrite / Editは権限プロンプトなしで通り、セッションを削除するとディレクトリごと消えます。

プロジェクトファイル用

専用のworktree

バックグラウンドセッションは、ファイルを編集する前に.claude/worktrees/配下の専用git worktreeへ移ります。セッションとサブエージェントの書き込みはそのworktreeに隔離され、他のセッションの作業コピーとぶつかりません。

worktreeへの移動は、次の場合にスキップされます。

  • ←や/backgroundで、すでに開いていたセッションをバックグラウンドへ移した場合(元の場所で編集を続けます)
  • セッションがすでにリンクされたgit worktreeの中にいる場合
  • 編集するファイルが、別のリンクされたworktreeの中にある場合
  • 作業ディレクトリがgitリポジトリではなく、WorktreeCreateフックも設定されていない場合
  • 書き込み先が作業ディレクトリの外にある場合

gitが使いにくいリポジトリでは、worktree.bgIsolationを"none"にして隔離そのものを切ることもできます。その場合セッションは作業コピーを直接編集し、コミットやブランチ切り替えの前に確認を求めます。

additionalDirectoriesは権限だけ拡張するで扱った設定は、この2つとは別の軸です。ファイルアクセスの範囲を広げる設定であり、$CLAUDE_JOB_DIR/tmpのWrite / Editが確認なしで通る扱いとは関係しません。承認の確認そのものを別の画面へ流したいなら、権限確認プロンプトをChannelsで中継する実装手順のような設計になります。

セッションを消すと何が残り、何が消えるか

claude rm <id>かagent viewのCtrl+X2回で、セッションを削除できます。消えるものと残るものは次のとおりです。

対象削除後
~/.claude/jobs/<id>($CLAUDE_JOB_DIR)とtmp削除後削除される
会話のトランスクリプト削除後ローカルに残り、claude --resumeで参照できる
Claudeが作ったworktree(変更なし)削除後安全に消せるなら一緒に消える
worktree(コミット前の変更あり)削除後claude rmでは残り、セッションの行も残る。agent viewのCtrl+X2回では変更ごと消える
worktree(未pushのコミットあり)削除後削除が拒否され、ブランチ名と未pushの件数が表示される

残したい変更は、先にコミットしておきます。

未pushのコミットを捨ててよいなら、拒否メッセージに表示された値を渡してclaude rm <id> --discard-unpushed <commit>@<worktree-id>で再実行します。このオプションはv2.1.260以上で使えます。

gitやWorktreeRemoveフックがworktreeを消せなかったときは、同じく表示された値を使う--force-remove-worktree <worktree-id>があります。v2.1.268以上で、リンクされたworktreeが.claude/worktrees/配下にあり、追跡ファイルに未コミットの変更がなく、ほかのセッション記録が名指ししていない場合に提示されます。この場合ブランチはリポジトリに残ります。

.claude/worktrees/が膨らむ原因は、この「残る」ケースの積み重ねです。tmpは後片付けが要らない代わりに、消えて困るものは置かない設計にします。

稼働状況をファイルから追わずに見たいときは、claude daemon statusが使えます。ドキュメントには、supervisorに到達できるか、プロセスID、バージョン、ソケットのディレクトリ、稼働中のセッション数を返すと書かれています。空のHOMEでsupervisorが動いていない状態で実行すると、v2.1.289では次のように出ました。

claude daemon status
# => not running
# =>
# => bg sessions:
# =>   sock dir:     /tmp/cc-daemon-501/f45c6ab8
# =>   control.sock: unreachable (connect ENOENT .../control.sock)
# =>   bg workers:   0 in roster.json (control unreachable)
# =>   roster.json:  absent
# =>   daemon.log:   absent

supervisorとセッションプロセスの関係

バックグラウンドセッションを動かし続けるのは、supervisorという常駐プロセスです。最初にセッションをバックグラウンドへ送るか、agent viewを開いた時点で自動的に起動します。各セッションはsupervisor配下の独立したClaude Codeプロセスで、状態によって扱いが変わります。

  • 作業中、権限プロンプトなどのダイアログ待ち、またはアタッチ中: プロセスは動き続けます。サブエージェントやモニターが動いていれば作業中として数えます
  • 完了または次の入力待ちのまま、未アタッチで約1時間: supervisorがプロセスを止めて、リソースを解放します。会話はディスクに残り、次にアタッチするか返信すると続きから再開します。Ctrl+Tでピン留めすると止まりません
  • supervisor稼働中に予期せず終了した場合: supervisorがプロセスを再起動します
  • 自動アップデート後: supervisorが新バージョンへ自分で再起動し、アイドル状態のセッションを裏で引き継ぎます

プロセスが止まったり再起動したりしても、そのセッションが始めたバックグラウンドのシェルコマンド、動的ワークフロー、バックグラウンドのサブエージェントは次のプロセスへ引き継がれます。止めたいときは、環境変数CLAUDE_CODE_DISABLE_BG_EXIT_HANDOFFを1にします。セッションを削除すれば、引き継がれていたものも止まります。

CLAUDE_CONFIG_DIRを設定していると、supervisorは~/.claudeではなくそのディレクトリを使い、別のセッション群を持つ独立したインスタンスとして動きます。

ローカルで動くことの意味

バックグラウンドセッションはローカルマシン上で動きます。$CLAUDE_JOB_DIRもローカルディスクにあるため、マシンをまたいで共有する領域としては使えません。マシンがスリープしても、復帰時にプロセスが再開されます。シャットダウンすると稼働中のセッションは止まります。

並列数にも注意が要ります。バックグラウンドセッションは、対話セッションと同じようにサブスクリプションの使用量を消費します。10本を並列で走らせれば、1本のときのおよそ10倍の速さで枠が減ります。

シェルから直近の出力を見たいときはclaude logs <id>です。表示されるのはセッションの最近の出力で、$CLAUDE_JOB_DIR/tmpの中身を見るコマンドではありません。

まとめ

置き場は、消えてよいかどうかで決まります。そのセッションの中で使い捨てるものは$CLAUDE_JOB_DIR/tmp、あとでコミットや共有をしたいものはworktree側です。

この記事を共有:XはてブLinkedIn