Claude Code reads a production feature flag at version 41. The flag routes five percent of checkout traffic through a new fraud model. The agent proposes changing it to ten percent, runs its checks, and produces a clear review packet.
A reviewer approves the change.
Before the MCP worker executes it, an operator adds a country exclusion and creates version 42. The queued agent call then sends set_percentage: 10. The adapter reads the current flag, applies the percentage, and writes a full replacement document based on the agent’s old copy.
The percentage is correct. The country exclusion disappears.
Nothing in the approval covered version 42. The write was still authenticated, authorized, and valid according to the API schema. Those controls answered whether the agent could edit the flag. They did not answer whether it could edit the flag after the facts used for approval had changed.
All systems, identifiers, and records below are fictional examples.
Approval needs a state boundary
A review packet normally binds approval to the requested action:
approved_effect:
resource: feature-flag/checkout-fraud-model
mutation:
traffic_percentage: 10
environment: production
approval_id: approval-example-241
That record is too loose. It names the target and mutation but omits the state that made the decision acceptable.
The reviewer saw version 41. The risk assessment depended on its current percentage, exclusions, model version, and owner. Those facts belong in the approval:
state_bound_effect:
resource: feature-flag/checkout-fraud-model
observed_version: 41
observed_etag: "flag-example-v41"
preconditions:
traffic_percentage: 5
country_exclusions_digest: sha256:example-a1
model_version: fraud-model-example-7
owner: payments-risk
mutation:
traffic_percentage: 10
effect_hash: sha256:example-effect-41
approval_id: approval-example-241
expires_at: 2026-09-12T14:30:00Z
The exact preconditions should reflect why the change was approved. Binding only the percentage would miss the changed exclusion. Binding every byte can create noise when harmless metadata changes. Pick the fields that alter impact, authority, rollback, or reviewer judgment, then include the resource version as the final concurrency guard.
This extends the idea behind rejecting a Claude Code plan built from a mixed snapshot. A coherent plan can still become stale after review. The execution path needs to detect that later change.
Put the comparison inside the write
A worker might read version 42, compare it with version 41, and stop before calling the write endpoint. That helps, but it leaves a race. Another actor can create version 43 after the check and before the update.
The target service must test the version and commit the mutation atomically. Depending on the datastore or API, that may use an ETag, row version, compare-and-swap, conditional update, or transaction predicate.
PATCH /feature-flags/checkout-fraud-model
If-Match: "flag-example-v41"
Idempotency-Key: op-example-912
Content-Type: application/merge-patch+json
{"traffic_percentage":10}
If version 41 is still current, the service applies the narrow patch and returns a new version. If version 42 exists, it returns a conflict without changing anything:
HTTP/1.1 412 Precondition Failed
ETag: "flag-example-v42"
The important property is atomicity. A separate GET, followed by an unconditional PATCH, recreates the original gap with nicer logging.
Prefer a narrow patch to replacement of the full object. Conditional replacement protects against the race, but a narrow mutation also reduces how much stale material the client can carry into the request.
A conflict is a new decision, not a retry
Agent runtimes are trained by experience to recover from errors. That instinct is dangerous here. A 412 does not mean the network had a bad moment. It means the world no longer matches the approval.
The worker should create a conflict receipt:
state_conflict_receipt:
operation_id: op-example-912
resource: feature-flag/checkout-fraud-model
expected_version: 41
observed_version: 42
expected_effect_hash: sha256:example-effect-41
write_committed: false
automatic_retry_allowed: false
required_action: replan_and_request_approval
Claude Code can read version 42 and propose a revised change. It cannot silently update observed_version and reuse the old approval. The new state may contain a safety condition that changes the decision, as the country exclusion did here.
Keep the rejected attempt in the review packet. It proves the boundary worked and tells the next reviewer why a fresh decision is needed. The receipt should come from the gateway or target service, not from the agent’s summary.
Batch operations need one declared consistency rule
The same problem becomes less obvious when an agent updates several resources. Suppose a rollout changes a flag, a rate limit, and a routing rule. The first write may succeed before the second encounters stale state.
Choose the behavior before execution:
batch_state_policy:
resources:
- id: feature-flag/checkout-fraud-model
expected_version: 41
- id: rate-limit/checkout-risk
expected_version: 18
- id: route/checkout-risk-v2
expected_version: 7
commit_mode: atomic_or_compensated
on_any_conflict: stop
partial_commit_allowed: false
Use a transaction when the systems share a transactional boundary. Across services, reserve versions or stage changes, then commit through a coordinator with explicit compensation. Do not let Claude Code improvise partial completion after a conflict.
If partial effects are unavoidable, record each committed version in the effect receipt. Recovery must compare the current version again before compensation. Otherwise rollback can overwrite a legitimate change that happened after the failed batch.
Test the race, not only the happy path
A serial eval will rarely catch this bug. Put another actor between planning and execution:
- Change a safety field after approval but before dispatch. Expect no write.
- Change the resource after the worker’s final read but before commit. Expect the atomic precondition to reject it.
- Return
412, then let the runtime retry with the new ETag. Expect the retry to be denied. - Change harmless display metadata. Verify whether policy permits a fresh automated check or requires review.
- Create a three-resource batch with one stale member. Expect the declared batch rule, not partial improvisation.
- Commit the write, then change the resource before compensation. Expect rollback to stop on the new version.
- Reuse the operation ID with a new state version. Expect an intent mismatch.
Assert authoritative state after every case. A neat conflict receipt is not enough if the adapter performed the write before producing it.
My rule is simple: approval belongs to the action and the state the reviewer saw. If either changes, stop. Bind the decision to a version, enforce that version inside the write, and treat a conflict as a request for a new decision.
Claude Code: Building Production Agents That Actually Scale covers the permission, MCP, eval, rollback, and review controls needed when coding agents can change real systems.