Claude Code を長く動かしていると、ごくまれに、これに出くわす。
Edit が「成功」と返ってくる。ログにも success と出る。なのに、ファイルを開くと、何も変わっていない。エージェントは成功したと信じて次に進み、変わっていないファイルを前提に作業を重ねていく。気づいた時には、土台が崩れている。
これは「受領証(成功という報告)」と「実際の効果(ファイルが本当に変わったか)」がずれる現象だ。最近この症状の根本原因がはっきりしたので、機構と、自分で検知する方法をまとめる。
犯人は、編集の内容ではない
最初に疑うのは「特殊な文字や長すぎる文字列のせいでは?」だと思う。矢印やチェックマーク、入れ子のmarkdown、バッククォート。
だが、これらは原因ではない。同じ材料(長くて記号の多い new_string)を、短い普通のセッションで Edit にかけても、毎回ちゃんと反映される。読み戻して grep -c で数えても全部入っている。
つまり、変数は「文字列の中身」ではなく「セッションの状態」の側にある。
本当の原因: 一時領域がいっぱい(ENOSPC)
実際に報告された根本原因はこれだった。子プロセスが一時領域に書き込もうとして、容量が尽きて失敗する(ENOSPC)。ところが、ツールの層は「成功」の受領証を返してしまう。
Command output was lost: the temp filesystem ... is full (0MB free).
The child process's stdout/stderr writes failed with ENOSPC.
これ一つで、長いセッションで起きる複数の症状が説明できる。
- Editが成功と報告するのに変わらない: 書き込みがENOSPCで失敗しているのに、受領証は成功
- まとめて送ったツール呼び出しが消える: 作業領域への書き込みが失敗し、結果が空になる
- Bashの出力が落ちる・1行目しか実行されない: 標準出力の一時領域への書き込みが失敗
紛らわしいのは、メインのディスクには空きがあること(手元では109GBも空いていた)。失敗しているのは、メインのディスクではなく、その一時領域の小さな区画だ。
検知: 沈黙の失敗を、大声の失敗に変える
一番こわいのは、これが「沈黙」で起きることだ。エラーで止まってくれれば気づける。成功と報告されて進まれると気づけない。だから、進む前に空きを確かめる。
まず、本当に使われている一時領域の区画を見る。メインのディスクではなく、一時領域そのものを見るのが肝心だ。
df -h "${CLAUDE_CODE_TMPDIR:-$TMPDIR}"
ここが小さい容量だったり、ほぼ100%使用だったりしたら、それが犯人だ。そして、作業の前に空きが足りなければ大声で警告する一行を挟む。
T="${CLAUDE_CODE_TMPDIR:-${TMPDIR:-/tmp}}"
avail=$(df -Pk "$T" | awk 'NR==2{print $4}')
[ "$avail" -lt 51200 ] && echo "WARN: 一時領域の空きが50MB未満。Edit/Bashの成功報告が嘘かもしれない" >&2
回避: 一時領域を広いところに移す
エラーメッセージ自身が示す回避策が効く。一時領域を、容量に余裕のある場所に移す。
export CLAUDE_CODE_TMPDIR=/path/to/roomy/dir
ただし、回避より検知の方が大事だ。回避は容量を増やすだけで、また埋まれば同じことが起きる。検知は、埋まった瞬間に沈黙ではなく大声で気づかせてくれる。
本質: 成功と報告するなら、本当に成功していなければならない
なぜ一時領域が埋まるか(並列のタスクが食い合うのか、区画に上限があるのか)は環境による。だが本質は、その理由とは独立している。
書き込みが失敗したなら、成功の受領証を返してはいけない。 成功と報告されたのに何も起きていない、という沈黙の食い違いが、一番危ない。下流の全部が、起きていない受領証を信じて進んでしまうからだ。
長いセッションで Edit の挙動が怪しいと感じたら、まず一時領域の空きを疑う。そして、受領証を鵜呑みにせず、編集の後に読み戻して確かめる癖をつける。
この種の「沈黙のデータ消失」や、トークンの暴走、権限の事故を、送出の前に止める安全装置と月次の点検チェックリストを、無料で置いています。
本記事は「受領証を鵜呑みにせず、編集の後に読み戻して確かめる」検知の癖までを扱った。沈黙のデータ消失を含む事故の型ごとに、何が引き金になり、送出の前のどこで止められるのかを、実際に起きた事例とあわせて 事故防止本(¥800・無料の章あり)にまとめている。検知のその先で、再発そのものを減らしたい人へ。