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

「記事をpushして、AIが『完了しました』と言ったのに、Zennに反映されていなかった」——クラウドルーティンを動かしていると、この種の「サイレント失敗」を踏む確率が思ったより高い。
結論
AIの「完了しました」は、外部プラットフォームへの反映確認ではない。検知したいなら、AIが通過したと言う点の直後に決定論的な結果検証を挟む。
今回の実験では、以下の3層を組み合わせた:
- push到達検証:
git merge-base --is-ancestorでSHAが origin に landing したか確認 - 外部プラットフォーム確認: ZennのRSSフィードを機械取得して公開記事数を確認
- 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つ以内)
- push後にSHA検証を入れる:
git merge-base --is-ancestor "$SHA" origin/mainが通らなければ「失敗」扱いにする。これだけでゴーストpublishを防げる - 外部プラットフォームのRSSをcronで取得する: 記事数が増えない日が続いたらSlack通知する仕組みを週次で回す
- 「AIに任せている箇所」を一覧にする: どのステップをAIの自己報告で判定しているか書き出すと、検証が抜けている点が見える
masatoman のメルマガ — 毎週月曜の朝に手紙を 1 通
masatoman.net の今週の記事 1 本を、読者目線で深掘りした手紙が毎週月曜 9:00 に届きます。「これ自分のことだ」が見つかる予告編。登録特典に「個人開発の収益化チェックリスト 15 項目」。
masatoman のメルマガ — 毎週月曜の朝に 1 通
masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。