Claude Codeのバックグラウンド実行、失敗したコマンドが「exit code 0」と誤報告される問題を追った
Claude CodeでBashコマンドをバックグラウンド実行すると、失敗したコマンドが完了通知でも出力ログの末尾でも「exit code 0」と誤って報告される事例が報告されている。GitHub Issueでの検証スレッドと、デプロイ作業で自分の身を守るための確認手順をまとめた。
エンジニアのゆとです。
Claude Codeにデプロイスクリプトを回させて、バックグラウンドに逃がして、しばらくしたら「完了しました(exit code 0)」って通知が来る。それを信じて次の作業に進んだら、実は本番には何も反映されていなかった——という報告をGitHubで見つけて、正直ちょっと背筋が冷えた。自分も長時間かかる処理はrun_in_backgroundに投げっぱなしにして、通知だけ見て「はい次」とやることが割とある。これが本当なら、自分も同じ目に遭っていて気づいていないだけかもしれない。
報告自体はまだ1週間ちょっと前のもので、コミュニティが検証を重ねている最中の話だ。結論から言うと「絶対にこうすれば再現する」という条件はまだ特定できていない。ただ、原因が分からないなりに今すぐできる自衛策ははっきりしているので、そこも含めて整理しておく。
何が起きたか——本番デプロイが「成功」と報告されて実際は失敗していた
報告者はrun_in_background: trueで、あるいは600秒のフォアグラウンドタイムアウトを超えて自動的にバックグラウンドへ移行したBashコマンドで、同じセッション内で少なくとも3回、同じパターンの誤報告に遭遇したと書いている。
一番具体的な再現例はこれだ。
DISTRIBUTION=internal bash scripts/deploy-distribution.sh
タスク完了通知はこう表示された。
Background command “Deploy task 311 + migration 184 fix to Internal” completed (exit code 0)
ところが、同じタスクの出力ログの末尾を実際に読むと、こう書いてある。
744687e86503: Pushed
failed commit on ref "layer-sha256:e3aaccdb2f96...": failed to do request: Put "https://asia-southeast1-docker.pkg.dev/...": net/http: timeout awaiting response headers
[exited with code 0]
docker pushがネットワークタイムアウトで失敗している。デプロイスクリプトの先頭にはset -euo pipefailが入っているので、本来ならこの時点でスクリプト自体が非ゼロで終了しているはずだ。それなのに、ログ末尾に自動で付与される[exited with code 0]という行と、タスク完了通知の両方が「成功」を報告していた。
これだけなら「たまたま1回」で済ませられたかもしれない。だが報告者は直後、echo "REAL_EXIT_CODE=$?" >> logfileという独立した確認手段を仕込んだ状態でもう一度同じデプロイを実行している。結果、ログファイルにはREAL_EXIT_CODE=1と正しく記録されていたのに、タスク完了通知は再び「exit code 0」と表示した。同じ失敗パターン(Artifact Registryへのレイヤーpushタイムアウト)で、2回連続、独立した再現が取れている。
さらに深刻なのは3件目のケースだ。同じセッションの前半で別のタスクのデプロイが「completed (exit code 0)」と報告され、それを信じて作業を続けた。数時間後、Cloud Runのリビジョン履歴を確認したところ、そのデプロイに対応するリビジョンが実際には作成されていなかったことが分かった。たまたま後続の別デプロイに同じコミットが含まれていたため実害には至らなかったが、「デプロイした」という誤った前提のまま何時間か作業が進んでいたことになる。
なぜこの問題が怖いか
Claude Codeがバックグラウンドコマンドの成否を判断する材料は、実質的に2つしかない。
- タスク完了通知に表示される
(exit code N) - 出力ログの末尾に自動で付与される
[exited with code N]
この2つが両方とも同時に間違っていた、というのが今回の報告の核心だ。片方が信頼できなくても、もう片方で気づけるなら実害は小さい。だが両方が同じ嘘をつくと、ログの中身を自分の目で最後まで読まない限り、失敗に気づく手段が実質なくなる。しかも今回のケースが「本番デプロイ」だったように、バックグラウンドに逃がすのはたいてい時間のかかる重い処理——つまりデプロイやビルド、マイグレーションのような、失敗したときの影響が大きい処理であることが多い。
コミュニティの検証で分かってきたこと——単純な再現はできていない
Issueが立ってから、複数の人が再現を試みている。ここが今回の話のややこしいところで、素直に再現できていない。
まず、原因の仮説として最初に出たのが「バックグラウンド実行時にコマンドを| loggerのようにパイプでラップしていて、pipefailが付いていないせいで$?がロガー側の終了コードを拾っている」という線だった。これが正しければ、バックグラウンド化したコマンドは基本的に全部この誤報告を踏むことになる。
ところが検証担当者がWindows・macOS・Linuxそれぞれで、以下のようなパターンを一通り試したところ、どれも正しくexit code 1を報告した。
| 検証パターン | 結果 |
|---|---|
run_in_background: trueで最初からバックグラウンド化、出力少量、exit 1 | 正しく1と報告 |
最初からバックグラウンド化、2万行以上の大量出力後にexit 1 | 正しく1と報告 |
フォアグラウンドで開始し、タイムアウトで自動的にバックグラウンドへ移行、104分実行後exit 1 | 正しく1と報告 |
set -euo pipefail下で、スクリプト途中のfalseが原因で異常終了 | 正しく1と報告 |
pipefail環境下でパイプライン中の一部が失敗 | 正しく1と報告 |
つまり「パイプでラップされて全部のバックグラウンドコマンドが誤報告する」という仮説は否定された。フォアグラウンドからバックグラウンドへの自動移行(元の報告の3件がすべて通った経路)を再現しても、Windows・macOS・Linuxいずれの検証者の環境でも正しい終了コードが返ってきている。
記事執筆時点で有力視されているのは、以下のような「元の報告に固有の条件」が絡んでいるという見立てだ。
docker pushのような、ネットワークI/Oの途中で失敗する処理特有の何か(sleepやfalseによる人工的な失敗では再現しない可能性がある)- 2万行規模を超える継続的な標準出力ストリームと、失敗タイミングの組み合わせ
- 報告者の実行環境(OS・ターミナル・Claude Codeのビルド)に固有の条件
要するに「誰にでも今すぐ起きる」というほど広範な不具合ではなさそうだが、単純な条件では再現できていない以上、「自分の環境では起きない」と決めつけていい根拠もまだない、というのが正直なところだ。

原因が特定されるまでの自衛策
再現条件が固まっていない以上、「これをやれば直る」という修正は今のところ提示できない。ただし、原因に関係なく今日から実践できる防御策はいくつかある。
1. 副作用のある処理は、通知だけで完了と判断しない
デプロイ・マイグレーション・本番データを触るスクリプトのような「失敗したら実害が出る」処理をバックグラウンドで回すときは、タスク完了通知の(exit code 0)を鵜呑みにしない。通知が来たら、出力ログの本文を実際に読みにいく一手間を挟む。今回の報告でも、ログの本文自体にはfailed to do requestという明確なエラー文字列が残っていた。ログの末尾の[exited with code N]という1行だけを見て判断するのが一番危ない。
2. スクリプト側に独立した成否マーカーを仕込む
報告者自身が実践していた回避策が地味に有効だ。
# Claude Codeの終了コード判定に頼らず、自分でログに成否を刻む
DISTRIBUTION=internal bash scripts/deploy-distribution.sh > logfile 2>&1
echo "REAL_EXIT_CODE=$?" >> logfile
この方法自体は「exit code 0」の誤報告を防げなかった(通知は相変わらず嘘をついた)が、ログファイルを開けばREAL_EXIT_CODE=1が一目で分かる状態は作れている。通知を信じるのではなく、ログの中に自分で判定材料を残しておくという発想が重要で、Claude Code側の挙動が直るまではこれが一番確実な保険になる。
3. デプロイ系の処理は、結果を外部から独立して確認する
一番手堅いのはこれだ。デプロイが「成功した」という情報をClaude Codeの報告だけに依存せず、実際にデプロイ先のプラットフォーム側で確認する。
# 例: Cloud Runなら、デプロイ後に実際のリビジョンを確認する
gcloud run revisions list --service=<service-name> --region=<region> --limit=3
# 例: Kubernetesなら、ロールアウト状況を直接見る
kubectl rollout status deployment/<deployment-name>
「Claude Codeが成功したと言った」ことと「実際に本番環境が変わった」ことは別の事実だ。今回のケースでは、この確認を怠ったことで数時間分の状況認識がずれていた。

4. PostToolUseフックで、バックグラウンドコマンドの結果を自動チェックする
もう一段仕組み化するなら、CLAUDE.mdやフックの側にルールを足しておく手もある。「デプロイ系のコマンドを実行した後は、必ず外部の状態確認コマンドを続けて実行する」という指示をCLAUDE.mdに明示的に書いておけば、通知の内容にかかわらず二重チェックが習慣として実行されやすくなる。
記事が見つかりません:

別の不具合と混同しないよう整理
同じ時期に、BashまわりではbypassPermissionsモードのcd複合コマンド誤検知という、また別の不具合も報告されている。方向性が逆なので混同しないようにしたい。
| 今回の誤報告 | cd複合コマンドの誤検知 | |
|---|---|---|
| 何が起きるか | 失敗したコマンドが成功と報告される | 安全なコマンドまで許可確認を求められる |
| リスクの向き | 気づかないまま実害が進む方向 | 作業が止まる方向(安全側) |
| 対象 | バックグラウンド実行したBashコマンド全般 | Read()拒否ルール設定下のcd複合コマンド |

まとめ
Claude Codeでバックグラウンド実行したBashコマンドが、実際には失敗しているのにタスク完了通知でもログ末尾でも「exit code 0」と報告される事例がGitHub Issueで報告されている。コミュニティによる検証では、単純なsleepやfalseを使った再現は今のところ成功しておらず、docker pushのようなネットワークI/O絡みの失敗や大量の標準出力ストリームなど、報告者固有の条件が絡んでいる可能性が高い。原因の特定にはまだ時間がかかりそうだが、デプロイやマイグレーションのような副作用のある処理をバックグラウンドで回すときは、通知のexit codeだけを信じず、ログ本文の確認・独立した成否マーカーの記録・外部プラットフォーム側での状態確認をセットにしておくのが今できる一番確実な自衛策だ。
Anthropic側の調査が進み次第、この記事も更新する。