Imagine an MCP adapter that starts a background job to update a staging routing rule. Claude Code asks for the change. Worker A picks it up, reads the rule, then stalls before its write reaches the service.

The supervisor decides the worker has failed. Its lease expires, worker B takes over, and B writes the corrected rule. A moment later, A resumes and sends its older update.

Both jobs can produce perfectly plausible logs. The final rule is still wrong if the service accepts A’s late write.

This is a hypothetical failure scenario, not a claim about Claude Code’s own worker implementation. It matters when teams put asynchronous MCP adapters or job queues behind their agents. Restarting the agent does not necessarily stop work it already dispatched.

A lease is not proof that the old process stopped

A lease says who may act during a bounded interval. It cannot force a paused process to disappear. That process may be waiting on a network connection, recovering from a long pause, or holding an outbound request that has not reached its destination.

A supervisor’s “worker replaced” event is therefore an incomplete safety signal. The service that accepts the mutation must reject the old holder.

Use a fencing epoch: an authority issues a monotonically increasing generation for a particular resource whenever ownership changes. In this example, A holds epoch 41. B receives epoch 42. After the handover, writes carrying 41 must fail.

These numbers are illustrative application fields. They are not built-in MCP configuration, and adding an epoch to a tool argument does nothing unless the receiving system enforces it.

The old worker resumes with epoch 41 after a handover to epoch 42. A resource-side gate rejects the old write and accepts the current holder.

Put the check beside the mutation

The tempting implementation checks ownership in the MCP adapter and then calls the destination API. That leaves a gap. The adapter can pause after the check, lose ownership, and resume the remote write later.

The destination needs an atomic decision: validate the current resource-bound authority and perform the mutation without a handover slipping between those steps. Depending on the system, that may mean a database transaction, a conditional update, or a resource service that owns both the lease state and the write.

Be precise about the guarantee. A destination that remembers only the highest epoch it has seen in a write can reject A after B writes. It may still accept A after the authority grants B ownership but before B’s first write arrives. If the requirement is to fence A immediately at takeover, the handover must update the destination’s fence before B is declared ready, or the destination must consult authoritative ownership atomically with the effect.

The fixture below takes the second approach inside one process. Ownership and the value live behind the same lock. That makes the test understandable; it does not make a Python lock a distributed coordination service.

Run a stale-worker takeover test

Download the standard-library Python fixture. It makes no network calls and changes no files. Run it locally:

python3 stale-worker-fence.py

The fixture exercises ten cases, including takeover before the successor’s first write, expiry without a replacement, immediate revocation, and rejection of a made-up higher epoch.

Its central failure test follows this sequence:

old = resource.acquire("worker-A", now=0)
new = resource.acquire("worker-B", now=10)
resource.write(new, "B's rule", now=11)
# Must raise PermissionError and leave B's rule unchanged:
resource.write(old, "A's stale rule", now=12)

The now argument is a deterministic test clock. A deployed service must supply its own authoritative time; the worker must not choose a timestamp that makes its expired lease look valid. Similarly, the fixture’s lease object stands in for a trusted ownership record. It is not an authentication mechanism.

The write gate compares the resource, worker, epoch and expiry with the authority’s current record. It rejects unknown future epochs too. “Greater than the previous number” is insufficient when an untrusted caller can invent that number.

Keep fencing separate from idempotency and approval

An idempotency key prevents a retry of the same logical operation from producing another effect, when the provider supports that guarantee. It does not decide whether an old worker still owns the resource. Two different operations can be individually idempotent and still arrive in the wrong order.

Fencing also does not prove that B’s proposed change is correct. B still needs scoped permission and any required human approval. If the change depends on an earlier read, the destination should enforce its expected resource version as well. Binding a write to the state that was approved addresses that separate problem.

Think of the epoch as an answer to one question: does this worker still hold the authority under which this write was dispatched? Do not make it stand in for the rest of the policy.

Turn the fixture into an adapter acceptance test

Before enabling automatic worker replacement, record this test in the adapter’s release checklist:

Test stepEvidence required
Pause A immediately before the destination mutationA has valid initial authority; the effect has not committed
Expire A and grant B ownershipThe resource-side fence advances before takeover is declared complete
Resume A before B’s first writeThe destination rejects A and leaves the resource unchanged
Let B write, then retry AB’s value survives; A receives a stale-authority rejection
Restart the ownership serviceDurable epoch state survives; old authority does not become valid again
Lose the write responseReconciliation determines the effect before a retry or new takeover

The local fixture covers the ownership decisions, not service crashes, network races or durable storage. Those need integration tests against the actual adapter and destination. Keep epochs durable across restarts and restore operations. Reusing an old generation can make an old request look current again.

Capture the operation ID, resource identity, worker identity and epoch in the effect receipt. Record rejected writes too, without copying secrets or entire payloads into the log. A missing receipt remains an unknown outcome; a review packet should not turn it into a pass.

When the provider cannot enforce a fence

Some external APIs offer neither a fencing field nor a conditional mutation that can enforce this ownership rule. Putting a proxy in front helps only if the proxy controls the effect boundary. Once it has dispatched a remote write, a local lease expiry cannot recall that request.

For those operations, automatic takeover may be the wrong choice. Stop new mutations, reconcile the in-flight effect, and require an operator decision before resuming. A timeout is not evidence that the old write failed. If the effect cannot be queried reliably, record that limitation and keep the workflow blocked rather than hiding it behind another retry.

The practical test is whether an old worker can still change the resource after the system says its replacement owns it. If it can, the handover is incomplete.

For the wider operating model around tool authority and production agent workflows, see my Claude Code book.