Parallel subagent file edits clobber each other in the IDE diff editor (N−1 spurious permission-denials); diff-review lock is per-agent, not per-IDE-connection

Open 💬 1 comment Opened Jun 12, 2026 by axtimwalde

Bug report: parallel subagent file edits clobber each other in the IDE diff editor

Title: Parallel subagent file edits clobber each other on the VS Code diff editor — N−1 spurious "permission denied" rejections (diff-review lock is per-agent, not per-IDE-connection)

Component: Claude Code CLI ↔ VS Code extension (IDE diff/review integration)

---

Summary

When the main agent fans out N≥2 subagents in parallel and each subagent performs a file Write/Edit while the VS Code diff-review path is active, only one edit survives. The other N−1 return a spurious Permission for this tool use was denied … the new_string was NOT written. Each subagent's diff briefly opens in the editor and is immediately replaced by the next subagent's diff; the superseded edits are rejected. Concurrent edits from a single agent do not exhibit this — they serialize correctly. The diff-review mutex is therefore scoped per-agent rather than at the IDE-connection level.

Environment

  • Claude Code 2.1.173 (AI_AGENT=claude-code_2-1-173_agent)
  • VS Code 1.124.0, Claude Code extension, remote (devcontainer / vscode-server), Linux
  • IDE transport: ws (from ~/.claude/ide/<port>.lock)
  • Edit review path active: edits are presented in the VS Code diff editor for accept/modify before commit
  • Session was itself a child session (CLAUDE_CODE_CHILD_SESSION=1) — see Notes

Impact / severity

Breaks any multi-agent pattern that fans out parallel subagents which each create or edit files (e.g., generating several drafts/files concurrently, one per subagent). N−1 of N parallel file-producing subagents fail with a misleading "permission denied," even though the user denied nothing. The failure is silent-by-misattribution: the agent believes the user rejected the edit.

Expected behavior

Concurrent edits originating from parallel subagents should serialize on the single shared diff editor — each diff presented and resolved (accept/modify/reject) in turn — exactly as concurrent edits from a single agent already do. None should be spuriously rejected.

Actual behavior

Only the subagent that holds the diff surface at the moment of user acceptance succeeds. The others' diffs are superseded before resolution and their Write calls return:

Permission for this tool use was denied. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). Try a different approach or report the limitation to complete your task.

Minimal reproduction

  1. Open a project in VS Code with the Claude Code extension connected (diff-review path active — i.e. edits open a diff for accept/modify).
  2. From the main agent, in a single turn, launch N≥2 subagents (Task/Agent tool) in parallel. Instruct each to perform exactly one Write to a distinct new file and report the verbatim tool result.
  3. Observe in the editor: each subagent's diff appears and is immediately replaced by the next. N−1 Write calls return the permission-denied string above; exactly 1 succeeds.

Controlled isolation (localizes the cause)

Two conditions, identical except for the agent boundary, diff path live in both:

| Condition | Concurrent writes | Result |
| --------------------------------------------------------------------------------- | ----------------- | ---------------------------------------------------------------------- |
| A — single agent: N Write calls in one message (main loop) | 4 | 4/4 serialized through the diff editor and succeeded; zero clobber |
| B — parallel subagents: 1 Write per subagent, N subagents launched together | 4 | 3/4 rejected (permission denied); 1 succeeded |

A and B differ in only one variable — whether the concurrent writes share an agent — and the outcome flips. Disk state confirmed: in B, the 3 rejected files were never written; only the survivor landed.

Timing corroboration: the surviving subagent blocked until the user accepted its diff (~56 s); the three losers each failed in ~7 s — i.e., at the instant their diff was superseded by the next subagent's openDiff.

Suspected root cause

The openDiff/review serialization lock is held at per-agent (per tool-runner / per conversation-loop) scope instead of at the IDE-connection scope. All subagents share one diff editor surface but do not share the lock, so each subagent's openDiff replaces the previous still-unresolved diff, and the superseded edit is rejected. Concurrent edits within one agent serialize because they contend on that agent's single lock; across subagents there is no shared lock to contend on.

This is consistent with a regression from a change to subagent execution/isolation that left the IDE diff lock behind (cross-agent serialization that previously held was lost).

Suggested fix

Move the diff-review gate to a single global queue keyed on the IDE connection, shared across the main loop and all subagents, so concurrent diffs from parallel agents queue and present one-at-a-time rather than clobbering. This does not require serializing the agents' work — reasoning and content generation can remain fully parallel; only the diff-_presentation_ step funnels through the shared queue.

Secondary issue (related, worth fixing together)

When the IDE bridge is disconnected, Write/Edit fail open: edits commit straight to disk with no diff and no review gate, silently (observed when the mcp__ide connection dropped mid-session; subsequent writes returned the direct-disk fast path with no diff shown). This silently voids the review guarantee. A dropped bridge should surface an error or otherwise not bypass review without notice.

Notes / non-reproductions

  • The repro session was a child session (CLAUDE_CODE_CHILD_SESSION=1); not established as required for the bug — the agent-boundary variable alone flips the result.

One-line repro for triage

With the IDE diff path live, launch N parallel subagents that each do one Write → N−1 spurious permission-denials, 1 success.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗