Claude Code prepares a production deployment for commit 8bc911d. The review packet looks sound, so an engineer approves the release. The deployment request reaches the platform, but the response times out.
The agent retries. Its transcript still contains the approval, the tool call still looks allowed, and the second request carries the same broad approved: true flag.
The first request had already created the release. The retry creates another one with a fresh operation ID. One human click has now authorized two production writes.
This is an approval replay bug. The problem is not that a human failed to review the work. The approval was represented as durable context instead of a single-use capability. Once issued, it could be copied into retries, subagent messages, and later tool calls.
For consequential MCP actions, approval should authorize one exact operation, against one target and payload, before a deadline. The tool gateway should consume that authority atomically when it dispatches the call.
Bind approval to the proposed effect
An approval record should describe what the operator saw. A generic task name such as deploy payments leaves too much room for drift.
approval_request:
request_id: apr-7f42
run_id: cc-run-4811
operation_id: deploy-payments-8bc911d-prod
tool: deploy.create_release
target:
service: payments-api
environment: production-eu
payload_digest: sha256:4b91...
expected_effect:
commit: 8bc911d
release_slots_created: 1
traffic_percent: 0
policy_version: release-policy-v12
evidence_digest: sha256:a830...
expires_at: 2026-08-20T10:58:00Z
max_redemptions: 1
Bind the request to the payload digest rather than the friendly summary alone. If Claude Code changes the image tag, environment, traffic setting, or release flags after approval, the digest changes and the capability no longer matches.
The expected effect matters too. It gives the gateway and verifier a concrete claim to check. In this example, approval permits one release slot with no production traffic. It does not permit a second release, a direct traffic shift, or a deployment to another region.
Keep this record in a trusted approval service. The model may quote its ID, but it should not be able to edit the scope, expiry, or redemption limit.
Issue authority that narrows, not a Boolean
After the engineer approves the request, issue a short-lived capability:
approval_capability:
capability_id: cap-a913
request_id: apr-7f42
subject:
run_id: cc-run-4811
agent_identity: claude-code/release-agent
operation_id: deploy-payments-8bc911d-prod
tool: deploy.create_release
target_digest: sha256:118c...
payload_digest: sha256:4b91...
policy_version: release-policy-v12
expires_at: 2026-08-20T10:58:00Z
remaining_redemptions: 1
This is narrower than human_approved: true. It cannot silently authorize another tool, another payload, or another run. A child agent should not inherit it unless the approval explicitly names that child identity.
Do not put a bearer secret in the transcript or review packet. Pass an opaque capability reference through the runtime and resolve it at the gateway. Logs can retain the capability ID and digest without exposing reusable authority.
Redeem and dispatch in one atomic step
A check followed by a separate update leaves a race. Two workers can both observe remaining_redemptions: 1, then both dispatch.
The gateway needs one atomic redemption operation:
UPDATE approval_capabilities
SET remaining_redemptions = 0,
redeemed_at = :now,
redeemed_by_operation = :operation_id
WHERE capability_id = :capability_id
AND remaining_redemptions = 1
AND expires_at > :now
AND operation_id = :operation_id
AND payload_digest = :payload_digest
AND policy_version = :policy_version;
Continue only when exactly one row changes. A second worker gets zero rows and must stop.
Redemption and external dispatch also need a stable operation ID. If the process crashes after consuming approval but before it receives the provider response, do not mint another approval or create another operation. Reconcile the existing operation through the provider journal and an authoritative read.
The state machine should preserve that uncertainty:
approval_execution:
capability_id: cap-a913
operation_id: deploy-payments-8bc911d-prod
redemption: consumed
dispatch_state: outcome_unknown
retry_policy: reconcile_same_operation_only
new_dispatch_allowed: false
outcome_unknown is not permission to try again. It is a demand to discover whether the approved effect happened.
Write an approval redemption receipt
The review packet should show how the approval was used, not merely that somebody clicked a button.
approval_redemption_receipt:
capability_id: cap-a913
request_id: apr-7f42
approved_by: release-engineer-204
approved_at: 2026-08-20T10:48:12Z
redeemed_at: 2026-08-20T10:48:19Z
operation_id: deploy-payments-8bc911d-prod
payload_digest: sha256:4b91...
gateway_decision: allow_once
provider_receipt: release-9912
observed_effect:
release_id: rel-9912
commit: 8bc911d
traffic_percent: 0
effect_verified: true
further_redemptions: denied
This receipt connects the human decision, runtime authority, dispatched operation, and observed production state. It also makes duplicate attempts visible. Record denied redemptions as boundary events with worker identity and reason.
The receipt belongs beside the diff, test evidence, and rollback record. It should be easy for a reviewer to answer: What was approved? Who or what used the approval? Did the approved effect occur? Could the same authority be used again?
Test replay, drift, expiry, and races
The happy path is the least interesting test. Add eval fixtures for the places where approval often leaks:
approval_capability_evals:
- case: two_workers_redeem_at_once
expect: one_dispatch_and_one_replay_denial
- case: timeout_after_provider_commit
expect: reconcile_same_operation_without_new_dispatch
- case: payload_changes_after_approval
expect: deny_payload_digest_mismatch
- case: policy_changes_before_dispatch
expect: deny_stale_policy_version
- case: capability_expires_in_queue
expect: deny_expired_capability
- case: child_agent_copies_capability_reference
expect: deny_subject_mismatch
- case: operator_revokes_before_redemption
expect: deny_revoked_capability
- case: completed_operation_is_requested_again
expect: return_existing_receipt_without_new_effect
Run the race fixture against the real storage transaction. A mock that evaluates requests one after another cannot prove atomic redemption.
Also test the gap between redemption and dispatch. Kill the gateway after the approval is consumed, after the provider commits, and before the response is stored. Every path should converge on the same operation and effect receipt without a second production write.
A human approval is useful only if the system preserves its boundaries. Bind it to the exact operation and payload, expire it quickly, consume it atomically, then record the effect. Claude Code should never turn one careful decision into unlimited permission to retry.
Claude Code: Building Production Agents That Actually Work contains practical patterns for MCP permissions, human approval, rollback, effect verification, evals, and review packets around production coding agents.