Qwen Code 0.21.5: stop duplicate MCP writes after disconnects
Quick answer
Qwen Code 0.21.5 changes what happens when an MCP connection disappears after a tool may already have completed its work. It now replays the current call only when the MCP server is trusted, the workspace is trusted, and the tool explicitly declares either idempotentHint: true or a consistent read-only annotation. Missing, false, or conflicting annotations stop automatic replay and return an unknown-outcome error instead.
That is the correct default for writes such as sending a message, creating a ticket, charging a card, publishing content, or appending a record. A dropped response does not prove the operation failed. Retrying blindly can turn one approved action into two committed side effects.
Upgrade to 0.21.5 or newer, but do not stop at the version check. Classify every MCP tool, verify the server and workspace trust boundary, force a post-commit disconnect in a disposable test server, and rehearse manual outcome reconciliation. The rollout passes only when unsafe tools execute once, safe tools replay only under every required gate, and a reconnected server cannot inherit stale annotations.
Who this is for
This guide is for Qwen Code users, MCP server authors, and teams that expose external systems to coding agents. It matters most when tools can create, update, delete, send, publish, bill, deploy, or otherwise change state.
The problem is different from forked-subagent prompt isolation: that boundary controls which instructions another agent can see. This one controls whether one approved MCP invocation can be executed again after its response is lost. Keep the broader MCP conformance checklist for protocol and transport coverage.
What changed in 0.21.5
The merged Qwen change describes the failure precisely: an MCP server can commit a side effect and then lose the response connection. Before the fix, the reconnect path could rediscover the server and send the same tools/call again even when annotations were missing or explicitly non-idempotent. The PR's independent harness reproduced a counter reaching two, and repeated disconnects could drive one user invocation to as many as four server executions.
Version 0.21.5 makes replay eligibility conservative and checks it twice: before reconnect and again after tool rediscovery.
| Tool state | Automatic replay after connection loss | Why |
|---|---|---|
Trusted server + trusted workspace + readOnlyHint: true
|
Eligible | The tool declares no environmental change |
Trusted server + trusted workspace + idempotentHint: true
|
Eligible | Repeating identical arguments should add no effect |
| Missing annotations | Blocked | MCP defaults do not establish replay safety |
idempotentHint: false |
Blocked | A repeated write may duplicate the side effect |
| Conflicting read-only annotations | Blocked | The client cannot establish a consistent safe claim |
| Untrusted server or workspace | Blocked | Tool metadata is not a security claim from an untrusted source |
| Safe before reconnect, unsafe after rediscovery | Blocked | Replacement process metadata must be checked again |
The MCP specification calls these fields hints, not guarantees. It says clients must not base security decisions on annotations from untrusted servers. It also defaults readOnlyHint and idempotentHint to false when absent. Qwen's two trust gates reduce accidental replay, but they do not prove that a trusted server described its tool truthfully.
A post-commit disconnect test
1. Freeze the client and inventory tools
Record the exact Qwen Code version, install channel, MCP server source, effective trust setting, workspace trust state, and the discovered tool annotations. Do not enable trust: true merely to recover transparent retries. Trust means Qwen may rely on server-supplied replay metadata.
Classify each tool as read-only, idempotent write, non-idempotent write, or unknown. Treat unknown as non-replayable.
2. Use two disposable canary tools
Build a local test MCP server with no production credentials:
-
increment_then_dropincrements a durable counter, closes the first response connection, and has no safe annotation. It models a non-idempotent write. -
set_status_then_dropwrites a value under a stable idempotency key, closes the first response connection, and declaresidempotentHint: true. Repeating it must leave one logical result.
Add a read-only lookup tool with readOnlyHint: true. Keep the counter, discovery count, and received arguments in a temporary test store. Do not use email, payment, deployment, or production APIs for this lab.
3. Run the unsafe matrix
For increment_then_drop, test missing annotations, idempotentHint: false, untrusted server, and untrusted workspace. Every case must produce exactly one server invocation, zero replay calls, and an unknown-outcome response that does not leak arguments or the upstream error.
Then start with an eligible tool, replace the server during reconnect, and remove the safe annotation or trust. Rediscovery may complete, but the replacement process must receive zero replay calls.
4. Run the safe controls
For set_status_then_drop, enable server trust, use a trusted workspace, and declare idempotentHint: true. One bounded replay may occur, but the durable store must still contain one logical status transition. Repeat with the read-only lookup tool and confirm replay does not mutate state.
Never label a counter increment, append, send, charge, or create-with-random-ID operation idempotent just to make the test green. Add a stable request key and server-side deduplication first.
5. Reconcile unknown outcomes before retrying
When Qwen says the result may be unknown:
- Freeze automatic and manual retries.
- Record the tool name, redacted argument hash, target, time window, and correlation or idempotency key.
- Query external truth through a read-only endpoint, audit log, or
getoperation. - Mark the result
committed,not_committed, orstill_unknown. - Retry only
not_committed, with an explicit human decision and the same stable idempotency key. Escalatestill_unknowninstead of guessing.
This is the same evidence principle used in the Qwen Goal completion checklist: transport success is not business-state proof.
Eight rollout gates
| Gate | Required evidence |
|---|---|
| Version | The launching process reports Qwen Code 0.21.5 or newer |
| Inventory | Every exposed MCP tool has an owner and replay class |
| Trust | Server and workspace trust are explicit and independently justified |
| Defaults | Missing or conflicting annotations produce no replay |
| Unsafe canary | Post-commit disconnect yields one invocation and unknown outcome |
| Safe control | Read-only or truly idempotent tools replay without extra effect |
| Rediscovery | Replacement tool metadata and trust are revalidated before replay |
| Reconciliation | The team can prove committed/not-committed before manual retry |
Use a compact evidence record for every canary:
qwen_version: 0.21.5
tool: increment_then_drop
server_trusted: true
workspace_trusted: true
annotations: missing
server_invocations: 1
replay_invocations: 0
external_state: committed_once
client_result: unknown
manual_retry: blocked
verdict: pass
Common mistakes
Treating connection loss as failure. The response is unknown; the side effect may already be committed.
Trusting an annotation from an unreviewed server. MCP annotations are descriptive hints. Review the implementation and test the behavior before trusting the server.
Marking a write idempotent without a stable key. Identical arguments do not help if every call creates a new resource or appends another row.
Fixing the client but not the server. Client-side replay guards reduce duplicates. Durable idempotency keys, outcome lookup, and deduplication at the tool boundary remain the stronger guarantee.
FAQ
Does Qwen Code 0.21.5 guarantee exactly-once execution?
No. It prevents automatic replay when safety cannot be established. Exactly-once business behavior still requires a server-side idempotency design or a durable operation record.
Should all MCP writes set idempotentHint: true?
No. Set it only when repeated calls with identical arguments have no additional effect. Otherwise leave it false or absent and provide a read-only way to reconcile an unknown result.
Top comments (0)