Claude Code calls an MCP tool to create a release ticket. Ten seconds pass. The client reports a timeout, so the agent tries again.
A minute later, two tickets appear. Both contain the same change. Both trigger the release workflow. The first call did not fail. Its response failed to reach the agent.
That distinction matters whenever an agent can change something outside its local working tree. A timeout tells the caller that no result arrived. It does not say whether the server rejected the request, completed it, or kept working after the connection closed.
The outcome is unknown. Treating unknown as failed turns a network problem into a duplicate side effect.
give every write an identity before sending it
A safe retry starts before the first attempt. Create an operation ID and bind it to the exact intent, target, and artifact involved in the change.
operation:
id: release-ticket-payments-7ac81f2
tool: jira.create_issue
intent: create_release_ticket
target: PAYMENTS
artifact_commit: 7ac81f2
request_digest: sha256:91f4...
requested_at: 2026-07-29T09:31:14Z
retry_policy:
max_attempts: 2
reuse_operation_id: true
verify_before_retry: true
The identifier must reach the system that owns the side effect. Keeping it in the Claude Code transcript helps with review, but it cannot stop Jira, a deployment service, or a billing API from performing the action twice.
Put the ID in a native idempotency field when the service has one. Otherwise use an external reference, deployment annotation, commit marker, or another field that the service can enforce and query. Repeating the same operation ID should return the earlier result or reject the duplicate. It should not create a second object.
Also bind the ID to a request digest. Reusing one ID with different arguments is a contract error, not a clever way to recover a run.
classify timeout as an unknown outcome
Agent tool results need more detail than success and failure. Use at least three states:
success
The system accepted the action and returned evidence of the resulting state.
rejected
The system explicitly refused the action and returned a reason.
unknown
The caller lost the response and cannot yet prove whether the action happened.
A validation error is a rejection. The caller knows the system did not accept the request and may be able to correct it.
A timeout, broken connection, process crash, or lost callback is different. The server may have committed the change one millisecond before the connection disappeared. Retrying at that point is a new write against an uncertain state.
This is familiar distributed-systems work, but agent frameworks can hide it behind a neat tool result. The model sees tool_error: timeout and confidently chooses the next action. That presentation is dangerous. The run record should say outcome: unknown and offer only safe recovery paths.
verify the external state before retrying
The agent needs a read path that can find the result by operation ID. After a timeout, query the target system rather than asking the model to infer what probably happened.
Call the tool once.
If it succeeds:
Verify the expected external state, then continue.
If it explicitly rejects the request:
Stop or prepare a corrected request under policy.
If it times out:
Mark the outcome unknown.
Query by operation ID.
If the query confirms success:
Continue without another write.
If the query confirms absence:
Retry with the same operation ID, within the retry limit.
If the state cannot be established:
Stop for review.
Verification should check more than existence. Confirm that exactly one matching object exists and that its important fields match the intended action. A release ticket with the right external ID but the wrong repository or commit is not successful evidence.
verification:
query: jira.search_by_external_id
operation_id: release-ticket-payments-7ac81f2
expected_count: 1
expected_fields:
project: PAYMENTS
commit: 7ac81f2
issue_type: Release
This read-after-write check also catches partial outcomes. A deployment tool may create a release record but fail before starting the rollout. A support tool may create a case without attaching the requested evidence. The next action depends on the actual state, not on a broad completed label.
do not ask a prompt to repair unsafe tool semantics
Some APIs have no idempotency key and no reliable lookup. A stronger instruction cannot make them safe to retry.
Put a wrapper in front of a legacy API when possible. The wrapper can reserve an operation ID, store the request digest, apply the action once, and save the external result. If it sees the same ID again, it returns the stored result instead of calling the API again.
operation_ledger:
operation_id: release-ticket-payments-7ac81f2
request_digest: sha256:91f4...
status: committed
external_id: PAYMENTS-8421
first_attempt_at: 2026-07-29T09:31:14Z
completed_at: 2026-07-29T09:31:23Z
The ledger narrows the problem, but it does not erase every race. The wrapper can crash after the external service accepts the request and before the result is stored. You still need reconciliation that inspects the external system.
If neither idempotency nor verification is possible, restrict the tool. Keep it read only, require a human to perform the mutation, or accept proposals without letting the agent execute them. Autonomous retry is the wrong default when the system cannot establish what the first attempt did.
preserve uncertainty in the review packet
A polished agent summary can hide the most important fact in the run. “The tool timed out, then the retry succeeded” sounds reassuring while ignoring a possible duplicate action.
The review packet should preserve the uncertainty and the decision that followed:
tool_call:
operation_id: release-ticket-payments-7ac81f2
result: timeout
outcome: unknown
verification_attempts:
- query: external_id=release-ticket-payments-7ac81f2
result: verification_service_unavailable
retries: 0
external_state: unverified
agent_decision: stopped_for_review
That is a good run. The task did not finish, but the agent refused to turn missing evidence into another side effect.
Record the target, operation ID, request digest, attempt count, timeout, verification queries, results, and final decision. This gives an operator enough evidence to reconcile the action without replaying it blindly. It also gives your eval suite real failure cases.
Test the awkward points deliberately:
- The server commits the write, then the response disappears.
- Verification returns two matching objects.
- The first attempt is still processing when verification runs.
- A retry reuses the ID with changed arguments.
- The operation ledger crashes before recording the external ID.
- The verification service is unavailable for longer than the run limit.
A passing result is sometimes a clean stop. Completion rate is not a useful reliability metric if the system completes work by duplicating external actions.
review write-capable MCP tools before granting access
Before Claude Code receives a write-capable tool, ask:
Can the caller supply an operation ID?
Does the server enforce that ID across retries?
Can the result be queried by the same ID?
Which errors prove rejection?
Which failures leave the outcome unknown?
How many attempts are allowed?
How long can an operation remain in progress?
Who owns reconciliation when verification fails?
Put the answers in the tool contract, wrapper, retry policy, and run record. Do not bury them in a prompt paragraph. The agent should receive a narrow set of valid next actions for each result.
The same rule belongs in your production readiness review. The free Claude Code production checklist helps map tool permissions, evidence, retry limits, stop conditions, review ownership, and rollback before you widen access.
build the production loop around the uncertain moments
Most demos exercise the happy path: the tool answers, the object appears, and the agent moves on. Production work lives in the uncertain middle. Responses disappear. Providers recover late. A system applies half the requested change. The agent needs an operating rule for each of those moments.
My rule is simple: after a timed-out write, Claude Code verifies before it retries. If it cannot verify, it stops.
I wrote Claude Code: Building Production Agents That Actually Scale for engineers building that wider production loop. It covers safe tool use, bounded retries, task contracts, evals, observability, review packets, rollback, and release gates. The Kindle edition is available on Amazon.
When those tools touch protected enterprise systems, Securing Enterprise AI Agents covers identity, bounded authority, MCP governance, policy enforcement, revocation, and incident evidence. The Enterprise AI Agents in Production bundle brings the engineering and security models together.
A timeout is missing information. Make the agent recover the information before it repeats the action.