Consider a hypothetical release workflow. 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.

Claude Code tool timeout verification flow

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. The YAML below is an illustrative integration contract, not native Claude Code or MCP configuration; tool names and identifiers are synthetic.

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 an external reference or deployment annotation can help you locate the effect, but it does not prevent duplicates unless the service enforces that guarantee. Verify the provider’s scope, retention window and handling of concurrent requests before permitting retries.

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 returns no match:
Keep the outcome unknown. Check lookup authority and the original attempt.

If the provider enforces a still-valid idempotency key for this exact request:
A bounded retry may be allowed under current write authority.

Otherwise, retry only with authoritative evidence of no effect
AND proof that the original attempt cannot produce a later effect.
Check current authority and the retry budget first.

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.

an empty lookup is not proof that nothing happened

Suppose the ticket search returns zero matches. That looks like permission to retry, but the search index may be behind the write path. The first ticket can exist before search can find it. The original request might also still be running.

Read permissions create another trap. A credential that can create a ticket does not necessarily see every ticket in the project. A successful search response can be incomplete for the question you are trying to answer.

Use these cases in the retry review:

  • Search index lag: keep the outcome unknown. Use an authoritative operation-status endpoint or wait under a bounded reconciliation policy. Waiting alone does not prove absence.
  • Restricted visibility: stop and obtain an appropriately authorised status check. Do not widen the agent’s credentials just to make the search return more data.
  • Original request still running: block a non-idempotent retry. Request cancellation if appropriate, then verify a terminal outcome and any resulting effects. A cancellation acknowledgement is not completion evidence.
  • Provider-enforced idempotency: a retry can recover the response if the exact request and key are unchanged, the key remains valid in the correct scope, and the provider covers the relevant side effects. Check authority and budget too.
  • Authoritative no-effect result and original attempt fenced or terminal: a bounded retry may be considered under current authority. A terminal status alone is insufficient if partial effects remain.

An external-reference field is only a correlation aid unless the service enforces uniqueness atomically. A search-then-create wrapper can race with another caller. Do not label it idempotent because both requests carried the same string.

Carry the evidence into the next shift

Download the empty-lookup retry review worksheet. It records the lookup’s visibility and consistency limits separately from the original operation’s lifecycle. Its default decision is to hold the write. The worksheet is a blank review artifact, not enforcement software or built-in MCP configuration.

For a tabletop exercise, give the reviewer a synthetic search response with zero matches and a provider status of running. The expected decision is to hold, even with attempts left in the retry budget. Next, change the status to cancelled but leave the effect evidence missing. The decision should remain hold. Only introduce retry eligibility after the evidence establishes one of the two valid paths above and current authority permits it. This exercise has not been run against a provider integration.

For the journal fields and reconciliation flow behind that decision, see why a missing result is not proof of failure.

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. Record who owns reconciliation before granting the agent write 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.

For the wider workflow around tool permissions, evals and human review, see Claude Code: Building Production Agents That Actually Scale, available on Kindle.

A timeout is missing information. Make the agent recover the information before it repeats the action.