45395 - シコウサクゴ -

exit code 0は『動いた』を意味しない:意図検証を仕様の一級市民にする

2026-05-17
AI駆動開発
AI駆動開発
Claude Code
運用設計
仕様駆動開発
意図検証
Last updated:2026-05-24
11 Minutes
2141 Words

定期実行ジョブを Claude Code に組ませて、ローカルテスト OK・本番 launchd 登録 OK・ログにも exit 0。「動いた」と思って翌朝確認したら、生成すべきファイルが1件も存在しない

これは AI 駆動開発で何度もハマったパターンです。本記事は、「exit code 0 = 正常動作」ではないという当たり前を、仕様駆動の中で一級市民の検証項目に格上げする運用ルールの記録です。

事の発端:「成功」したジョブが何も生成していなかった

ある集計ジョブを Claude Code に作ってもらい、launchd で日次実行に登録しました。翌日ログを確認:

1
2026-05-16 03:00:01: job started
2
2026-05-16 03:00:14: job completed, exit code: 0

完璧に見えました。ところが下流のパイプラインが「入力ファイルがありません」とエラーを吐いていました。出力ディレクトリを覗くと、昨日のファイルが存在しない

ジョブのロジックを読み直すと、引数で渡される日付フォーマットが本番環境と違って、内部で try / except が握りつぶしていました。エラーは起きていたが、exit code は正常返しになっていたのです。

なぜ exit code 0 を信じてしまうのか

人間の認知バイアスとして、**「プロセスが正常終了した = 仕事が終わった」**という思い込みがあります。これは Unix の伝統に強く根ざしていて、シェルスクリプトでも CI でも $? を見るのが基本動作です。

しかし、現代のジョブはほぼ全て 「処理を実行する」 + 「生成物を残す」 の二段構えになっています。exit code が示すのは前者だけです。

観点exit code が答えるもの答えないもの
プロセス終了正常終了したか-
ロジックエラーを吐かなかったか意図通りに動いたか
生成物-ファイルが出来たか
鮮度-出来たファイルが新しいか
件数-期待される件数か

つまり exit code は 「プロセスが死ななかった」 という極めて弱い保証しかしません。それを「成功」と読み替えるのが事故の元です。

意図検証を仕様の一級市民にする

この問題への対処は、Requirements 段階で「成功の定義」を生成物ベースで書くことです。

NG な書き方(プロセス指向)

1
## 受入基準
2
- ジョブが exit 0 で終了すること
3
- ログにエラーが出ないこと

これは前述の事故を防げません。両方とも満たしながら、生成物が0件のパターンが成立します。

OK な書き方(生成物指向)

1
## 成功の定義
2
このジョブは以下を満たす場合に「成功」とする:
3
4
1. 出力ディレクトリ: /data/aggregates/daily/
5
2. 期待ファイルパターン: aggregate_YYYY-MM-DD.parquet
6
3. 鮮度閾値: 実行日と同日付のファイルが存在
7
4. 最小件数: ファイル内のレコード数 >= 100
8
5. 下流互換性: スキーマが下流ジョブの期待と一致

5つすべてを満たして初めて「動いた」と言えます。exit code はその十分条件ではなく、必要条件の一つに過ぎないという位置付けです。

デプロイ後の機械検証

この「成功の定義」を、デプロイ後に機械検証するスクリプトに落とし込みます。

verify_intent.sh
1
#!/bin/bash
2
TODAY=$(date +%Y-%m-%d)
3
OUTPUT_FILE="/data/aggregates/daily/aggregate_${TODAY}.parquet"
4
5
# Check 1: ファイル存在
6
[ -f "$OUTPUT_FILE" ] || { echo "FAIL: file not found"; exit 1; }
7
8
# Check 2: 鮮度(24時間以内)
9
FILE_AGE=$(($(date +%s) - $(stat -f %m "$OUTPUT_FILE")))
10
[ $FILE_AGE -lt 86400 ] || { echo "FAIL: file too old ($FILE_AGE sec)"; exit 1; }
11
12
# Check 3: 件数
13
RECORD_COUNT=$(python -c "import pandas; print(len(pandas.read_parquet('$OUTPUT_FILE')))")
14
[ $RECORD_COUNT -ge 100 ] || { echo "FAIL: too few records ($RECORD_COUNT)"; exit 1; }
2 collapsed lines
15
16
echo "PASS: intent verified"

このスクリプトを launchd の 別ジョブとして登録します。本体ジョブの 1 時間後など、適切なタイミングで実行されるようにします。

ポイントは「本体と別プロセスで検証する」ことです。同一プロセス内に検証を組み込むと、本体が死んだ場合に検証も走らず、結局気づけないままになります。

なぜ AI agent はこれを忘れがちなのか

AI agent にジョブ実装を依頼すると、ほぼ確実に 「処理ロジック + try/except + logging」 までは作ってくれます。しかし 「生成物の検証」 を最初から提案してくることは稀です。

理由を考えると、

  • 一般的なチュートリアルが「処理が完了したらログを出して終わり」で終わるパターンが多い
  • exit code = 成功という前提が広く共有されている
  • 生成物の検証は「運用」の領域とされ、「実装」の責務外と認識されている

つまり、agent の常識は 「実装」と「運用」の境界を引いているのです。しかし現代のジョブはその境界を越えて初めて価値を出すので、Requirements 段階で agent に境界を越えさせる指示を明示的に与える必要があります。

CLAUDE.md に書き込む

1
## 仕様駆動開発:意図検証の原則
2
3
Requirements 段階で「成功の定義」セクションを必ず含めること。
4
exit code 0 や「ログにエラーなし」だけでは成功とみなさない。
5
6
成功の定義に含めるべき項目:
7
- 出力ディレクトリと期待ファイルパターン
8
- 鮮度閾値(最新ファイルが何時間以内に生成されているか)
9
- 最小件数(または件数の妥当性基準)
10
- 下流ジョブが消費できる形式であること
11
12
デプロイ後検証のチェックスクリプトを併せて設計すること。
13
本体ジョブとは別プロセスで実行する仕組みにする。

これを書いておくと、Claude Code は Requirements を起こす際に必ず「成功の定義」を質問してきます。

三層の検証構造

ジョブの「動作確認」を整理すると、以下の3層になります。

層1: プロセス層

  • exit code
  • 標準エラー出力の有無
  • プロセスが規定時間内に終了したか

これは launchd や Cron、CI が自動で見てくれる部分です。

層2: ロジック層

  • ユニットテストが通る
  • 統合テストが通る
  • ローカル実行で期待値が出る

これは開発時に CI で担保される部分です。

層3: 意図層(最重要)

  • 本番環境で実際に生成物が出ているか
  • 生成物の鮮度・件数が妥当か
  • 下流が消費できる形式か

層1と層2が OK でも、層3で落ちることが頻繁にあります。本記事の発端ケースも、層1・層2はパスしていました。

「あの時動いたのに今動かない」を防ぐ

ジョブを長期運用していると、以下のような失敗がよく起きます。

  • 環境差異: ローカルでは動くが本番で動かない(パス・権限・タイムゾーン)
  • 依存変化: 外部 API が仕様変更
  • データ変化: 入力データのスキーマが変わって parse 失敗
  • 時刻依存: 営業日カレンダーが切り替わって対象データが0件

これらすべて、exit code は正常を返す可能性があります。try/except で握りつぶされた瞬間にプロセスは「成功」終了するからです。

層3の意図検証だけが、これらを翌朝までに気づかせてくれます。

まとめ:「動いた」を再定義する

AI 駆動開発で繰り返しジョブを組ませる時代、「exit code 0 = 動いた」という素朴な等式は通用しません

「動いた」とは、

  1. プロセスが死ななかった(exit code)
  2. ロジックがエラーを吐かなかった(log)
  3. 意図した生成物が、意図した鮮度で、意図した件数で残った(intent)

この3つすべてを満たすことです。3番目を仕様駆動の Requirements 段階で一級市民の項目として書き込み、デプロイ後に機械検証する。これが AI 時代の運用設計の最低ラインだと考えています。

agent は「処理を書く」のは得意ですが、「処理が意図通り動いた」を検証する責務を仕様で与えないと、永遠に exit code を信じる実装を出してきます。仕様で枠を作るのが人間の仕事です。

Article title:exit code 0は『動いた』を意味しない:意図検証を仕様の一級市民にする
Article author:45395
Release time:2026-05-17

記事へのご質問・ご感想をお聞かせください

フィードバックを送る