AIエージェント開発・運用の事故録 #04
Stopを押しても、
仕事は止まらない
会話停止・process cancel・観測継続・rollbackを分ける
公開日:2026年8月25日 著者:飯田 友広(Tomohiro Iida)
この記事の結論
チャットのStopは会話の生成を止める操作であり、起動済みのworkflow、cron、deployは止まりません。
停止はNOT_DISPATCHED、RUNNING、CANCEL_REQUESTED、CANCEL_CONFIRMED、SUCCEEDED、FAILED、UNKNOWNの7状態で扱います。
取り消しを要求したことと、取り消しが確定したことを同じ状態にしません。
停止を決めたあとも観測は続けます。巻き戻しは停止とは別の操作です。
UNKNOWNはfail-closed側へ倒し、次のmutationを始めません。
AIエージェントに作業を任せていると、途中で止めたくなる場面があります。
このときチャットのStopボタンを押すと、応答の生成は止まります。ところが、その会話が起動したGitHub Actions、cron、deploy、DBマイグレーションは、多くの場合そのまま走り続けます。
結論は次のとおりです。
チャットの停止と、外部processの停止は別の操作です。
そして、停止の状態は1つではありません。取り消しを要求した状態と、取り消しが確定した状態と、観測できない状態を別々に持ち、観測できない状態は安全側へ倒します。
Incident Card
INCIDENT:
チャットのStopと、外部で走っているprocessの停止を同じ操作として扱っていた
SYMPTOM:
会話を止めたあともworkflow、cron、deployが進み、最終状態を誰も観測していない時間が生まれた
FALSE_ASSUMPTION:
Stopを押せば、その会話が起動した作業も一緒に止まる
ROOT_CAUSE:
会話の停止、取り消しの要求、取り消しの確定、観測、巻き戻しが1つの状態として扱われていた
IMMEDIATE_FIX:
会話を止めたあとも、起動済みjobのterminal stateを取り直すまで作業を完了扱いにしない
SYSTEM_FIX:
NOT_DISPATCHED / RUNNING / CANCEL_REQUESTED / CANCEL_CONFIRMED / SUCCEEDED / FAILED / UNKNOWNを別状態として持ち、UNKNOWNをfail-closed側へ倒す
REMAINING_RISK:
取り消し要求が届いたことと、副作用が発生していないことは別。巻き戻しはさらに別の操作である会話を止めても、外へ出た仕事は走り続けた
観測できた事実は次のとおりです。
- 会話を止めても、起動済みのGitHub Actions runは進みます
vercel.jsonのcronは会話と無関係に発火します。/api/cron/jobs-tickなど複数のジョブが10分間隔で登録されています- 本番系workflowは
concurrency.cancel-in-progress: falseのため、あとから別のrunを投げても割り込みで止まりません - 会話が終わったあと、terminal stateを誰も観測していない時間が生まれます
止まったのは会話の生成だけでした。外部システムの状態は、会話の有無とは独立に進みます。
誤った前提:Stopは1つの操作である
Stopボタン、会話終了、workflow cancel、deploy cancel、migration cancel、rollbackを同義に扱っていました。実際には、止まる対象も可逆性も違います。
| 操作 | 止まるもの | 止まらないもの |
|---|---|---|
| Stopボタン | 生成中の応答 | 起動済みjob、cron、deploy |
| 会話終了 | セッション | 外部システムの状態 |
| workflow cancel | 残りのstep | 実行済みstepの副作用 |
| deploy cancel | 配信前のbuild | 配信済みのdeployment |
| migration cancel | 未実行のmigration | 適用済みのDDL |
| rollback | 何も止めない | 前進を戻す別操作 |
この表の下2行が重要です。deployとmigrationは、途中で止めても「元に戻る」わけではありません。止めた地点までの副作用は残ります。
根本原因:停止を1つの状態として持っていた
停止を「止めた/止めていない」の2値で扱うと、次の3つが同じ値へ潰れます。
- 取り消しを要求したが、届いたかどうかは未確認
- 取り消しが提供側で確定した
- 状態を観測できない
3つ目が最も危険です。観測できないことを「たぶん止まった」と読むと、実際には走り続けているprocessの上で次の操作を始めます。
その場の対処:terminal stateを取り直すまで完了にしない
会話を止めたあとに行う最小の手順を固定しました。
1. この会話が起動した外部processを列挙する
2. 各processのstateを提供側APIから取り直す
3. terminal state(SUCCEEDED / FAILED / CANCEL_CONFIRMED)に到達したものだけ完了とする
4. 取り直せないものはUNKNOWNとして記録する
5. UNKNOWNが1件でもある間、次のmutationを始めない「会話を閉じたので終わりです」とは報告しません。閉じたのは会話であって、processではありません。
仕組みへの変換:7つの状態で停止を扱う
| 状態 | 意味 | 次にできること |
|---|---|---|
| NOT_DISPATCHED | 外部へまだ渡っていない | 取り消し不要。破棄でよい |
| RUNNING | 実行中 | 取り消し要求を出せる |
| CANCEL_REQUESTED | 要求を送った。到達も適用も未確認 | 観測を続ける |
| CANCEL_CONFIRMED | 提供側で取り消しが確定 | 副作用の有無を別途確認 |
| SUCCEEDED | 正常終了 | 巻き戻しを検討する |
| FAILED | 失敗して終了 | 部分適用の有無を確認する |
| UNKNOWN | 状態を観測できない | fail-closed。mutationを始めない |
CANCEL_REQUESTEDとCANCEL_CONFIRMEDを分けることが要点です。要求を送ったことは、こちら側の事実です。取り消されたことは、提供側の事実です。この2つを1つにすると、要求を出した時点で止まったものとして扱ってしまいます。
操作側は次の4つへ分けます。
STOP(conversation) -> 会話の生成だけを止める。外部processへは何も送らない
CANCEL(process_id) -> CANCEL_REQUESTED へ遷移させる。確定はしない
OBSERVE(process_id) -> terminal state を取り直す。停止後も続ける
ROLLBACK(target) -> 前進を戻す別operation。承認と証拠を要求する
if state == UNKNOWN:
TREAT_AS = RUNNING_OR_APPLIED
MUTATION = NOT_PERFORMEDobservation:停止を決めても、観測は止めない
停止したあとに観測をやめると、そのprocessは「止めたつもり」のまま記録から消えます。観測は、停止の判断とは独立して続けます。
このリポジトリでは、Stopそのものが終了ではなく検査点として実装されています。.claude/settings.jsonのStopには3つのhookが登録されており、そのうち.claude/hooks/autonomous-stop-guard.mjsは、agentが自分で実行できる待機・確認・後片付けをユーザーへ返して終わろうとする停止を拒否します。
同じhookは、background taskのうちcompleted、failed、cancelledのいずれでもないものをactiveとして扱います。走っているものがある状態は、終了状態ではありません。
rollback:停止と巻き戻しは別操作
止めることと戻すことを同じ操作に見せると、事故が起きます。
.github/workflows/deploy-signal-production.ymlは、deployが成功した時点で境界を記録します。Step SummaryにはState: DEPLOYEDと対象SHAが書かれ、続けて「後続jobはpost-deploy verificationであり、そこでの失敗はこのdeploymentを取り消さない」と明記されています。
つまり、workflowが赤くなっていても本番は更新済みという状態が正しく存在します。赤いworkflowを「未反映」と読んでblind rerunすると、二重の反映を起こします。
巻き戻しは専用のworkflowです。.github/workflows/production-rollback.ymlは、新しいコードを一切出さない復旧専用の経路として分けられています。実行には確認文字列の入力が要り、main以外からは動きません。さらに、入力された戻し先を信用せず、Vercelからmetadataを取り直して素性を検証します。
戻せるのは、source commitがorigin/mainの履歴にあり、かつ過去に本番として配信されたdeploymentだけです。未マージのPreviewへ「rollback」できるなら、それは復旧ではなく迂回です。
割り込みで止められない設計もある
すべてのprocessを「あとから来たものが古いものを殺す」形にはしません。本番系のworkflowはconcurrencyのcancel-in-progressをfalseにしています。
| 系統 | cancel-in-progress | 理由 |
|---|---|---|
| 本番deploy・migration・rollback | false | 途中で殺すと外部状態が壊れる |
| PRのCI・exact-head検査 | true | 途中で止めても外部状態は変わらない |
新しいrunで古いrunを止めてよいのは、途中で止まっても外部状態が壊れない検査系だけです。本番deployとrollbackは同じsignal-productionというconcurrency groupで直列化し、本番DBマイグレーションはproduction-db-migrationという別のgroupで直列化します。いずれも割り込みでは止めません。
groupを分ける理由は、deployとmigrationが互いを待つ必要がないためです。同じgroupへ入れると、片方の実行がもう片方を待たせます。止めない設計と、待たせない設計は別々に決めます。
migrationについても、.github/workflows/production-migration.ymlは手動実行専用で、確認文字列と、適用するつもりのmigration番号の入力を要求します。実際の未適用と食い違えば中止します。止める設計より前に、始める条件を絞っています。
再利用できる停止操作の対応表
止めたいものごとに、使う操作と確認すべきterminal stateが違います。
| 止めたいもの | 使う操作 | 確認するstate |
|---|---|---|
| AIの応答生成 | Stop | 会話側のみ。外部は未変化 |
| 起動済みのCI run | workflow cancel | run conclusionがcancelled |
| 本番への反映 | 反映前ならbuild中止 | 配信済みならrollbackへ切り替え |
| 適用中のmigration | 中止入力 | 適用済み番号をDB側で確認 |
| 配信済みの本番 | rollback | 戻し先の素性と配信履歴 |
このうち、下2行は「止める」ではなく「戻す」に分類されます。混ぜないでください。
Evidence
| 内容 | 分類 | 根拠 |
|---|---|---|
| 本番deploy・migration・rollbackは割り込みで止まらない | IMPLEMENTED | 3つのworkflowのconcurrency.cancel-in-progress: false |
| deploy成功後の失敗はdeploymentを取り消さない | IMPLEMENTED | .github/workflows/deploy-signal-production.ymlのdeployment boundary記録 |
| rollbackは確認文字列とmain限定と素性検証を要求する | IMPLEMENTED | .github/workflows/production-rollback.yml |
| migrationは手動実行専用で確認文字列と期待値照合を要求する | IMPLEMENTED | .github/workflows/production-migration.yml |
| Git連携による自動デプロイは全ブランチで無効である | IMPLEMENTED | vercel.jsonのgit.deploymentEnabled |
| cronは会話と無関係に発火する | IMPLEMENTED | vercel.jsonのcrons |
| Stopは検査点として3つのhookを通る | IMPLEMENTED | .claude/settings.jsonのStop |
| 終了状態でないbackground taskはactiveとして扱われる | IMPLEMENTED | .claude/hooks/autonomous-stop-guard.mjs |
| 会話を止めたあとに、terminal stateを観測していない時間が生まれた | OBSERVED | 運用時の観測 |
| 7状態モデルを全ての外部processへ適用した効果 | INFERRED | 状態定義は運用中。削減効果は未計測 |
| 取り消し要求から確定までの到達確認の自動化 | PROPOSED | 現時点では観測手順であり、自動照合は未実装 |
適用限界
- 状態名はNetsujoの運用語です。提供側APIの状態名とは1対1で対応しません。移すときは対応表を作ってください。
- 停止によって削減できた事故件数や時間は未計測です。この記事は状態分離と観測手順が存在する状態までを扱います。
- ここで扱うのはGitHub Actions、Vercel、DBマイグレーションを前提とした構成です。実行基盤が変われば、観測できる状態も変わります。
concurrencyの設定は「割り込みで止まるか」を決めるだけで、手動cancelを禁止するものではありません。
残るリスク
- 取り消し要求が届いたことと、副作用が発生していないことは別です。
CANCEL_CONFIRMEDでも部分適用は残りえます。 - UNKNOWNをfail-closedにすると、実際には終わっている作業で待ちが発生します。観測経路の可用性がそのまま運用速度に効きます。
- 巻き戻せる範囲は、過去に本番として配信された履歴に依存します。履歴に素性の記録がなければ、戻し先として選べません。
- cronのように会話と無関係に走るprocessは、停止設計の対象から漏れやすい位置にあります。列挙の抜けは設定では防げません。
まとめ
Stopは会話の生成を止める操作です。外部processを止める操作ではありません。
STOP(conversation)
+
CANCEL(process) -> CANCEL_REQUESTED
+
OBSERVE(process) -> CANCEL_CONFIRMED / SUCCEEDED / FAILED / UNKNOWN
+
ROLLBACK(target) は別operation
+
UNKNOWN は fail-closed止めたい対象を先に列挙し、対象ごとに操作と確認すべき状態を決めます。観測できない状態を「止まった」と読み替えないことが、この設計の中心です。
よくある質問
Stopを押したのに処理が進んでいるのはなぜですか
Stopは会話側の操作であり、起動済みの外部processへは何も送らないためです。GitHub Actionsのrun、cron、deployは、それぞれの提供側で独立に動きます。止めるには、対象ごとの取り消し操作を別に実行します。
workflowをcancelすれば、本番は元に戻りますか
戻りません。cancelは残りのstepを止めるだけで、実行済みstepの副作用は残ります。本番へ配信済みの場合、必要なのはcancelではなくrollbackです。Netsujoでは復旧専用のworkflowを分け、確認文字列と戻し先の素性検証を要求しています。
赤いworkflowは、本番が未反映という意味ですか
そうとは限りません。deploy jobが成功したあとにpost-deploy QAが失敗した場合、本番は更新済みでworkflowは赤くなります。反映の有無はworkflowの色ではなく、配信済みdeploymentと対象SHAで判断します。
状態が観測できないときはどうしますか
UNKNOWNとして記録し、実行中または適用済みの側へ倒します。次のmutationは始めません。観測できないことは、止まっていることの証拠にはなりません。
すべてのjobを割り込みで止める設定にすべきですか
検査系だけにしてください。途中で止まっても外部状態が壊れないものは、新しいrunで古いrunを止めても問題ありません。本番deploy、migration、rollbackは同じconcurrency groupで直列化し、割り込みでは止めない設定にしています。
この記事の著者

飯田 友広
代表取締役
Netsujo株式会社 代表取締役。京都発のWeb3・AI実装スタートアップを2023年6月に創業。Webサイトを営業基盤として捉え、経営・営業・検索・生成AI・コンバージョン・計測を横断して課題と改善優先順位を整理する「Netsujo SIGNAL」を設計・運営。京都ビッグデータ活用プラットフォーム参画(小規模企業会員・ベンチャー)、同プラットフォーム発のワーキンググループ「Chain Up KYOTO」参画(2026-03-10)、IVS2026サイドイベント「なぜ京都でWeb3.0ビジネスなのか」を京都府庁旧議場で開催(2026-07-02・Netsujoとして主催、企画・登壇・運営/京都府デジタル政策推進課は共催)。京都美術工芸大学での講義・龍谷大学でのセミナー実績、ITコミュニティ「みやこでIT」(connpassメンバー614名・イベント167件・2019年2月から運営)運営。NPO法人NEMTUS理事、BAR KRYPTO運営。ソーシャル企業認証「S認証」認証企業(2026年2月認証・2026年4月公表)。技術領域はWeb3/ブロックチェーン/DID/NFT/生成AI/コミュニティ運営。
プロフィールを見るこの記事が向いている方
AIエージェントに作業を任せ、途中で止めた作業の最終状態を追えていない開発責任者
workflow cancel、deploy cancel、rollbackを同じ操作として扱っているチーム
停止後の観測と巻き戻しを、担当者の記憶ではなく手順として持ちたい方
— 壁打ち相談
読者のよくある相談
記事を読んだ後に「自分の状況だとどう判断すべきか」を整理するための壁打ち相談を受け付けています。下記のような相談例が当てはまる方は、お気軽にご連絡ください。
Q. Stopを押したのに処理が進んでいる
Stopは会話側の操作です。外部processは対象ごとの取り消し操作で止めます。
Q. workflowが赤いが、本番に出たのか分からない
deploy成功後の失敗はdeploymentを取り消しません。配信済みdeploymentと対象SHAで判断します。
Q. 止めた作業の最終状態を誰も確認していない
terminal stateを取り直すまで完了扱いにせず、観測できないものはUNKNOWNとして記録します。
上記いずれかが該当する場合、初回30分の壁打ち相談で論点整理に対応します。記事に書ききれない個別事情を踏まえた判断材料が必要な段階こそ、壁打ちが活きやすいフェーズです。
関連するサービス
AI導入・開発運用設計のご相談
停止・取り消し・観測・巻き戻しを、別々の操作として設計する
AIエージェント運用の停止条件、CI、deploy、migration、rollbackの経路を一つの運用として整理します。