Skip to main content
日本語

Pressing Stop Does Not Stop the Work

Tomohiro Iida · Published August 25, 2026 · Updated August 25, 2026

When an AI agent is working on your behalf, there are moments when you want it to stop. Pressing Stop in the chat ends the response generation. The GitHub Actions runs, cron jobs, deployments, and database migrations that the conversation started usually keep going. Stopping a conversation and cancelling an external process are two different operations, and the state of a stop is not a single boolean.

Key takeaways

  • The chat Stop button ends generation only. Dispatched workflows, crons, and deployments continue.
  • Model a stop with seven states: NOT_DISPATCHED, RUNNING, CANCEL_REQUESTED, CANCEL_CONFIRMED, SUCCEEDED, FAILED, and UNKNOWN.
  • Requesting a cancellation and having a cancellation confirmed are different facts and must not share one state.
  • Observation continues after the stop decision, and rollback is a separate operation from stopping.
  • UNKNOWN is treated as fail-closed: assume the work is still running or already applied and start no further mutation.

Incident card

FieldValue
IncidentA chat Stop and the cancellation of an external process were treated as the same operation.
SymptomWorkflows, crons, and deploys kept advancing after the conversation ended, and nobody observed the final state.
False assumptionPressing Stop also stops the work that the conversation started.
Root causeConversation stop, cancel request, cancel confirmation, observation, and rollback were held as one state.
Immediate fixDo not mark work complete until the terminal state of every dispatched job has been re-read.
System fixHold seven separate states and push UNKNOWN to the fail-closed side.
Remaining riskA delivered cancel request does not prove that no side effect occurred, and rollback remains a further operation.

What we observed

The false assumption: stop is one operation

OperationWhat it stopsWhat it does not stop
Stop buttonThe response being generatedDispatched jobs, crons, deployments
Ending the conversationThe sessionExternal system state
Workflow cancelThe remaining stepsSide effects of completed steps
Deploy cancelA build before deliveryA deployment already delivered
Migration cancelMigrations not yet appliedDDL already applied
RollbackNothingIt moves state back; it is not a stop

Root cause: stop was held as a two-value state

Treating a stop as stopped or not stopped collapses three different situations into one value: a cancellation was requested but delivery is unconfirmed, a cancellation was confirmed by the provider, and the state cannot be observed at all. The third is the dangerous one. Reading “unobservable” as “probably stopped” means the next operation starts on top of a process that is still running.

Immediate fix: no completion before a terminal state

System fix: seven states

StateMeaningWhat may happen next
NOT_DISPATCHEDNever handed to an external systemDiscard; no cancellation needed
RUNNINGExecuting nowA cancel request may be sent
CANCEL_REQUESTEDRequest sent; delivery and effect unconfirmedKeep observing
CANCEL_CONFIRMEDCancellation confirmed by the providerCheck separately for side effects
SUCCEEDEDFinished normallyConsider rollback
FAILEDEnded in failureCheck for partial application
UNKNOWNState cannot be observedFail closed; start no mutation

Separating CANCEL_REQUESTED from CANCEL_CONFIRMED is the core of the model. Sending the request is a fact on our side; the cancellation is a fact on the provider side. Merging them means the moment a request is issued, the work is treated as stopped.

Observation continues after the stop

If observation ends when the stop decision is made, the process disappears from the record as “presumably stopped”. In this repository, Stop is an inspection point rather than a terminal state: .claude/settings.json registers three Stop hooks, and .claude/hooks/autonomous-stop-guard.mjs refuses a stop that hands agent-executable waiting, polling, or cleanup back to the user. The same hook treats any background task that is not completed, failed, or cancelled as still active.

Rollback is a different operation

.github/workflows/deploy-signal-production.yml records a deployment boundary as soon as the deploy job succeeds. The step summary states DEPLOYED with the subject SHA and notes that the following job is post-deploy verification and that a later failure does not undo the deployment. A red workflow with an updated production is therefore a valid state, and reading red as “not released” leads to a blind rerun and a double release.

Rolling back uses a dedicated recovery workflow that ships no new code. .github/workflows/production-rollback.yml requires a confirmation string, runs only from main, and does not trust the requested target: it re-reads the deployment metadata from Vercel. A rollback target is valid only when its source commit is in the history of origin/main and it was previously served as production. Rolling back to an unmerged preview would be a bypass, not a recovery.

Some processes are not interruptible by design

Workflow familycancel-in-progressReason
Production deploy, migration, rollbackfalseKilling them midway damages external state
Pull request CI and exact-head checkstrueStopping midway does not change external state

Production deploy and rollback share the signal-production concurrency group, while production database migration is serialised in its own production-db-migration group. Neither is interrupted by a newer run. The groups are separate because a deploy and a migration have no reason to wait for each other; putting them in one group makes one block the other. "Do not interrupt" and "do not queue behind" are separate decisions.

.github/workflows/production-migration.yml is manual only, requires a confirmation string, and takes the migrations you intend to apply as input; if that list disagrees with what is actually unapplied, it aborts. The entry condition is narrowed before the stopping behaviour is considered.

Reusable stop matrix

What you want to stopOperation to useState to confirm
AI response generationStopConversation side only; external state unchanged
A running CI jobWorkflow cancelRun conclusion is cancelled
A release in progressAbort before deliveryOnce delivered, switch to rollback
A migration in progressAbort inputApplied migration numbers in the database
A release already deliveredRollbackTarget provenance and production history

Evidence

ClaimClassSource
Production deploy, migration, and rollback are not interrupted by a newer run.IMPLEMENTEDconcurrency.cancel-in-progress false in the three workflows
A failure after the deploy job does not undo the deployment.IMPLEMENTEDDeployment boundary record in deploy-signal-production.yml
Rollback requires a confirmation string, main, and provenance verification.IMPLEMENTED.github/workflows/production-rollback.yml
Migration is manual and checks the expected migration list.IMPLEMENTED.github/workflows/production-migration.yml
Git-triggered deployment is disabled on every branch.IMPLEMENTEDgit.deploymentEnabled in vercel.json
Cron jobs fire independently of any conversation.IMPLEMENTEDcrons in vercel.json
Stop passes through three registered hooks.IMPLEMENTEDStop entry in .claude/settings.json
Background tasks that are not terminal are treated as active.IMPLEMENTED.claude/hooks/autonomous-stop-guard.mjs
There was a window with no observation of the terminal state.OBSERVEDOperational observation
The effect of applying the seven-state model everywhere.INFERREDThe state definitions are in use; the effect is unmeasured.
Automatic confirmation that a cancel request was delivered.PROPOSEDCurrently a manual observation step.

Limitations

Remaining risk

Frequently asked questions

Why does work continue after I press Stop?
Stop acts on the conversation and sends nothing to a dispatched external process. Actions runs, crons, and deployments each advance on their provider. Stopping them requires a separate cancel operation per target.
Does cancelling the workflow restore production?
No. Cancelling stops the remaining steps while the side effects of completed steps remain. If the release was already delivered, the required operation is a rollback through the dedicated recovery workflow.
Does a red workflow mean production was not updated?
Not necessarily. When the deploy job succeeds and post-deploy QA fails, production is updated and the workflow is red. Release status is judged by the delivered deployment and its subject SHA, not by the colour of the run.
What do we do when the state cannot be observed?
Record it as UNKNOWN and treat it as running or already applied. Do not start the next mutation. Being unable to observe is not evidence that something stopped.
Should every job be interruptible by a newer run?
Only the checking jobs. Anything whose interruption leaves external state intact is safe to cancel in progress. Production deploy, migration, and rollback are serialised in one concurrency group instead.

Previous: the same work item, different chat namesEpisode 03 covers how work is identified when it moves between tools.

Next: trust the exact SHA, not the pull request numberEpisode 05 aligns the code that was verified with the code that is operated on.

AI agent development and operations incident logThe series hub lists all twelve episodes and the order to read them in.

We separate stopping, cancelling, observing, and rolling back into distinct operations with recorded state.

Talk to Netsujo about AI development and operations design