Claude Code starts a release task. One MCP call asks the deployment service for a preview. Another queues a database compatibility check. A subagent prepares a ticket update. Then the operator sees the wrong commit in the run and presses stop.
The model stops producing tokens. The queue does not care.
Thirty seconds later, the compatibility job finishes and publishes its result. The ticket update follows. The deployment preview reserves a shared environment. The run was stopped, yet its authority kept moving through systems that had already accepted work.
Killing the client process is not a production stop control. I want a cancellation barrier that revokes the run’s authority, rejects work that has not started, reconciles work already in flight, and proves that no later effect can still appear under the old run ID.
Separate four meanings of stopped
Teams often use one word for four different states:
stop_states:
generation_stopped:
meaning: model is no longer producing output
dispatch_stopped:
meaning: gateway will not accept new calls from the run
queued_work_cancelled:
meaning: accepted calls that have not started cannot execute
effects_settled:
meaning: in-flight calls are reconciled and no unknown effect remains
Only the last state is safe enough for an operator to hear “the run has stopped.”
A terminal interrupt may achieve the first state. Closing the session may eventually achieve the second. Neither tells you what a remote queue, CI worker, deployment controller, or SaaS API will do with work it already holds.
This becomes harder with subagents. A parent run can stop while a child process still has a valid lease and a queued tool call. If the child identity is not tied to the parent’s authority, the supposed kill switch only removes the visible part of the run.
Put the run ID on every piece of work
Cancellation needs an address. Give every run an immutable ID, then propagate it into child runs, gateway decisions, queue messages, provider metadata, and effect receipts.
run_authority:
run_id: cc-run-4421
task_id: release-payments-8bc911d
root_lease: lease-90e4
child_runs:
- run_id: cc-run-4421-checks
lease: lease-90e4-checks
- run_id: cc-run-4421-ticket
lease: lease-90e4-ticket
valid_until: 2026-08-16T11:20:00Z
revocation_epoch: 17
Every queued message should carry at least run_id, lease, revocation_epoch, operation_id, and an expiry. A worker checks those fields when it starts, not only when the request enters the queue.
That second check closes a common race. A gateway can approve a call at 10:51:03, the operator can revoke the run at 10:51:04, and a worker can pick up the message at 10:51:12. If the worker trusts the old gateway decision without checking current authority, revoked work still runs.
This follows the same rule as checking MCP authority immediately before dispatch. Approval belongs to a specific capability at a specific time. It is not a permanent property of a queue message.
Use a cancellation barrier, not a fire-and-forget signal
A stop request should create a barrier record before the system starts cancelling components:
cancellation_barrier:
barrier_id: stop-cc-run-4421-01
run_id: cc-run-4421
requested_at: 2026-08-16T10:51:04.018Z
requested_by: operator:thomas
reason: wrong_commit_detected
revoke:
root_lease: lease-90e4
descendants: true
new_revocation_epoch: 18
queue_policy:
reject_not_started: true
retain_for_audit: true
in_flight_policy:
request_provider_cancel: true
reconcile_effects: true
completion_rule:
pending_calls: 0
unknown_outcomes: 0
active_leases: 0
Write the barrier durably first. If the cancellation worker crashes halfway through, another worker can resume from the same record. An in-memory flag disappears at exactly the moment you need it most.
The barrier also gives every service one ordering point. Work stamped with epoch 17 is stale after epoch 18 exists. A late message cannot regain authority because its timestamp looks earlier or because one cache has not refreshed.
Do not delete queued messages silently. Mark them cancelled and preserve their operation IDs. During an incident, you need to know which actions the system rejected and which ones had already crossed into execution.
Treat in-flight calls as unresolved until proven otherwise
Some providers support cancellation. Many only acknowledge that a cancellation request arrived. Others cannot cancel once an operation reaches a commit stage.
Record that difference:
in_flight_call:
operation_id: reserve-preview-payments-8bc911d
tool: deployment.reserve_preview
provider_job: preview-7712
state_at_barrier: running
cancellation:
requested_at: 2026-08-16T10:51:04.233Z
provider_response: accepted
effect_check:
expected_absence:
reservation_for_run: cc-run-4421
observed: none
checked_at: 2026-08-16T10:51:09.804Z
settled_state: cancelled_without_effect
provider_response: accepted is not the settled state. The provider may race with cancellation and commit the effect first. Query the authoritative system for the expected effect, just as you would after an external write with a missing response.
There are three useful outcomes:
cancelled_without_effect
The provider stopped and authoritative state shows no effect.
completed_and_verified
The operation crossed the commit point. Record the effect and start the approved recovery path.
unknown
The system cannot prove whether an effect occurred. Keep the barrier open and assign reconciliation to a person.
Do not translate unknown into cancelled. That makes the dashboard look tidy while leaving the operator blind to a possible production change.
Make descendants lose authority with the parent
A child run should receive less authority than its parent and should never outlive the parent’s lease unless a human explicitly promotes it to an independent task.
The gateway can enforce the relationship:
child_dispatch_check:
child_run: cc-run-4421-ticket
parent_run: cc-run-4421
child_epoch: 17
current_parent_epoch: 18
parent_lease_status: revoked
decision: deny_parent_revoked
external_call_sent: false
Do not rely on the parent to notify every child. The parent may be the process that crashed. Workers and gateways should resolve current authority from the control plane before starting consequential work.
Also check scheduled retries. A retry timer is queued authority in another form. When the barrier opens, cancel timers, callbacks, delayed messages, and workflow continuations attached to the run. Otherwise the system can look quiet for five minutes and wake up under stale authority.
Return a stop receipt that can be reviewed
The operator needs evidence, not a green icon. Produce a stop receipt only after the completion rule passes:
stop_receipt:
barrier_id: stop-cc-run-4421-01
run_id: cc-run-4421
authority:
root_lease: revoked
descendant_leases_revoked: 2
current_epoch: 18
work:
queued_cancelled: 3
in_flight_settled: 1
scheduled_retries_cancelled: 2
effects:
completed_and_verified: 0
cancelled_without_effect: 1
unknown: 0
last_settled_at: 2026-08-16T10:51:09.804Z
quiet_period_seconds: 30
verified_at: 2026-08-16T10:51:39.921Z
decision: stopped_and_quiet
The quiet period catches delayed callbacks and queue visibility lag. Its duration should match the systems involved rather than use one arbitrary global value. A local worker queue and a third-party deployment service have different settling behaviour.
Put this receipt in the review packet. If an effect completed before cancellation, include its effect receipt and rollback owner. If anything remains unknown, the final decision must say stop_incomplete and name the person holding the reconciliation task.
Test the stop path while nothing is on fire
A stop control that works only when every dependency is healthy is decoration. Add fixtures for the races you expect to see:
cancellation_evals:
- case: queued_call_starts_after_parent_revocation
expect: deny_parent_revoked
- case: provider_commits_during_cancel_request
expect: completed_and_verified
- case: cancellation_worker_crashes_mid_barrier
expect: resume_from_durable_barrier
- case: delayed_retry_fires_after_revocation
expect: deny_stale_epoch
- case: provider_status_unavailable
expect: stop_incomplete_unknown_outcome
- case: child_run_misses_parent_notification
expect: deny_on_gateway_authority_check
Run the test with a provider stub that deliberately commits during the cancellation race. Then break the status endpoint. The system should preserve the unknown outcome rather than invent a clean cancellation.
My rule is stricter than “the process exited.” A Claude Code run is stopped only when its authority is revoked, its pending work is drained, and every in-flight effect is settled or handed to a named operator.
Claude Code: Building Production Agents That Actually Work covers MCP boundaries, permission leases, effect verification, rollback, evals, observability, and review packets for production coding-agent work.