メインコンテンツへスキップ
← 記事一覧に戻る
·運営·9 min read

Claude Codeが「完了しました」と言って止まっていた—サイレント失敗を検知する設計を組んだ実験ログ

Claude Codeautomationroutinemonitoring個人開発
Claude Codeが「完了しました」と言って止まっていた—サイレント失敗を検知する設計を組んだ実験ログ

「記事をpushして、AIが『完了しました』と言ったのに、Zennに反映されていなかった」——クラウドルーティンを動かしていると、この種の「サイレント失敗」を踏む確率が思ったより高い。

結論

AIの「完了しました」は、外部プラットフォームへの反映確認ではない。検知したいなら、AIが通過したと言う点の直後に決定論的な結果検証を挟む。

今回の実験では、以下の3層を組み合わせた:

  1. push到達検証: git merge-base --is-ancestor でSHAが origin に landing したか確認
  2. 外部プラットフォーム確認: ZennのRSSフィードを機械取得して公開記事数を確認
  3. Slack通知: 失敗時のみ通知し、成功時は沈黙(アラート疲れ防止)

この記事の前提

  • スタック: masatoman.net(Next.js + Supabase)+ Zenn / Qiita 外部配信
  • 運用: content-executorというクラウドルーティン(CCR)で記事を自動生成→各媒体にadapt→push
  • 当事者として: weekly-plannerとcontent-executorを毎日稼働させている
  • 「設計したら成功した話」ではなく「失敗を踏んでから設計を直した判断記録」

読者のよくある詰まり

こういう状況で詰まった経験はないだろうか:

  • ルーティンが「pushしました」と言ったので信じていたら、翌日確認したらブランチに乗っていなかった
  • CIがpassしているのにZenn/Qiitaに記事が反映されていない
  • エラーメッセージが出ていないので成功だと思っていたが、外部プラットフォーム側で無言リジェクトされていた

これらの共通点は「AIやCIが"成功"を報告した後の、外部への到達確認が抜けている」こと。

実際に起きたこと

Zennデプロイが4日間無言停止していた

dev_journalに残っている事象(2026-06-23記録): Zennの記事titleが70字を超えていたために、Zenn側でデプロイが4日間中断していた。

問題の構造はこうだった:

GitHub Actions(CI) → frontmatter検証 → PASS
Zenn Connect(デプロイ) → title長でリジェクト → 無言で失敗

GitHub ActionsのCIはfrontmatterフィールドの有無を検証するが、ファイル名(=slug)の文字数はZenn本体のデプロイゲートが止める。pushまで気づけない構造だった。4日間、誰も気づかなかった。

気づいたきっかけはRSSフィードだった。zenn.dev/{username}/feed をGETすると公開記事リストを機械取得できる。公開記事数が増えていなかったことで、停止を検知できた。

slug 50字超でCI通過・4件が全停止

同じ時期に別の事象: Zennのslugが50字を超えると全デプロイが停止する仕様があり、CI(frontmatter lint)では検証されていなかった。1件修正したが残り3件(53/55/55字)が残っており、全体が止まり続けた。

「1件直せば終わり」と思ったが、実際は4件あった。CIのログには何も出ない。

CCRはローカルのSkillを読めない

もう一つのサイレント失敗パターン: ローカルの ~/.claude/skills/ を整備してCCRに適用しようとしたとき、CCRがそのファイルにアクセスできないことが判明した(2026-06-23記録)。

ルーティンはエラーを出さずに、Skillなしで動き続ける。「Skillを組んだから設計通りに動いているはず」という認識が崩れるまで、気づけない。

原因分析:なぜサイレント失敗が起きるか

サイレント失敗の構造は3パターンに分類できる:

パターンA: 自分の管轄外で失敗する
Zennデプロイはリポジトリ外(Zenn Connect側)で起きる。GitHub ActionsのCIはリポジトリ内の検証しかできない。外部プラットフォームの結果はダッシュボードにしか出ない。

パターンB: AIが「完了」と報告しても到達確認できていない
git push を実行してもネットワーク状態やブランチ保護で landing しないことがある。AIは「pushしました」と言える(コマンドをrunした)が、リモートに届いたかは別確認が必要。

パターンC: 環境前提が崩れても処理が続行される
CCRとローカルのファイルシステムは別環境。ローカルで整備した設定がCCRに届かなくても、ルーティンはデフォルト動作で続行する。エラーにならないから気づけない。

判断軸:どこに検証を挟むか

この問題への対処として、「AI報告の直後に決定論的な確認」を差し込む方針を取った。

Push到達確認(パターンB対策):

# pushした後に必ず実行
SHA=$(git rev-parse HEAD)
git fetch origin main -q
if git merge-base --is-ancestor "$SHA" origin/main; then
  echo "PUSH_OK"
else
  echo "PUSH_FAILED — 差し戻す"
fi

このワンライナーが成立するかどうかを「PUSH_OK」の定義にする。成立しなければ、DBのステータスを planned に戻して再試行可能にする。AIが「pushしました」と言っても、この確認が取れない限りは「公開済み」扱いにしない。

外部プラットフォーム確認(パターンA対策):

# ZennのRSSフィードで公開記事数を機械確認
curl -s https://zenn.dev/{username}/feed | grep -c "<item>"

記事を追加したのにcount が増えていなければデプロイが止まっている。Slack通知と組み合わせることで「気づかなかった」を防ぐ。

環境前提の明示的な確認(パターンC対策):

CCRが読めるファイルはリポジトリ内のみ。~/.claude/skills/ のような外部パスは使わず、masatoman.net/docs/adapt-spec-{媒体}.md をルーティンに直接Readさせる設計に変更した。

詳細はLLMの自動化は指示文でなく決定論的ゲートで守るで書いた。

今日やること(3つ以内)

  1. push後にSHA検証を入れる: git merge-base --is-ancestor "$SHA" origin/main が通らなければ「失敗」扱いにする。これだけでゴーストpublishを防げる
  2. 外部プラットフォームのRSSをcronで取得する: 記事数が増えない日が続いたらSlack通知する仕組みを週次で回す
  3. 「AIに任せている箇所」を一覧にする: どのステップをAIの自己報告で判定しているか書き出すと、検証が抜けている点が見える

masatoman のメルマガ — 毎週月曜の朝に手紙を 1 通

masatoman.net の今週の記事 1 本を、読者目線で深掘りした手紙が毎週月曜 9:00 に届きます。「これ自分のことだ」が見つかる予告編。登録特典に「個人開発の収益化チェックリスト 15 項目」。

masatoman のメルマガ — 毎週月曜の朝に 1 通

masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。

シェア

コメント

投稿にはログインが必要です(メールアドレスのみ・パスワード不要)。入力内容は保持され、ログイン後そのまま投稿されます。