fix(agent_servers): serialize ACP session-creation per connection - #50
Merged
Conversation
Two concurrent calls to `connection.new_session()` / `connection.load_session()` / `connection.resume_session()` on the same `AcpConnection` cause `claude-agent-acp` (and any other compliant ACP wrapper) to spawn two child claude SDK processes back-to-back. Each child then runs `npx <pkg>@latest` in parallel for its stdio MCP servers (chrome-devtools-mcp, server-github, etc). npm's tarball extraction isn't safe under concurrent invocations on the same cache directory: the loser silently fails to register its MCP child, leaving the active claude process without (e.g.) `chrome-devtools-mcp` and the agent visibly missing browser tools — the recurring "chrome MCP not working in spec tasks" bug. PR #46 deduped at the wrapper level so we get one `claude-agent-acp` per `(project, agent_id)`, but a single wrapper can still receive two distinct ACP `session/new` or `session/load` requests in flight (e.g. panel restoration loading the saved thread + something else creating a new session for a different worktree at startup), and each spawns its own claude SDK child. Fix: add `session_creation_chain: Rc<RefCell<Option<Shared<Task<()>>>>>` on `AcpConnection`. Each `new_session` / `open_or_create_session` call acquires the next slot in the chain — it awaits the previous request's completion before dispatching its own ACP RPC, and signals completion via a drop guard so cancellation/error releases the slot too. Verified by `test_concurrent_session_creation_is_serialized`: two distinct-session-id `load_session` calls dispatched together — only one RPC reaches the wire until the first responds, then the second fires. Note: this is a structural defence at the spawn point. The original trigger that caused two startup `session/new` calls (one for the saved thread, one for a "ghost" session with cwd matching the workspace's first worktree path) is still unidentified — likely in `agent_panel.rs::load()` or `MultiWorkspace`'s ActiveWorkspaceChanged flow, but conditions in saved KVP rule out the obvious paths. Tracking that as a follow-up; this fix prevents the symptom regardless of source. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
lukemarsden
added a commit
that referenced
this pull request
May 25, 2026
…, 2026-05-25) Records the second upstream merge stacked onto the 002029 feature branch: zero manual conflicts (ort strategy), one signature-drift repair for ThreadView::new / ConversationView (code_span_resolver from cfd0461), ancillary notes for the new ACP logout/additional-directories defaults that compose cleanly with PR #50, and E2E validation results. Spec-Ref: helix-specs@f5f507183:002029_merge-latest-zed
This was referenced Jun 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a per-
AcpConnectionchain so concurrentnew_session/load_session/resume_sessioncalls dispatch their ACP RPCs sequentially rather than in parallel.This is the next layer of the recurring "chrome MCP not working in spec tasks" bug (see helixml/helix#2352 for the corresponding sandbox-versions bump).
Why
Two concurrent calls to
connection.new_session()causeclaude-agent-acpto spawn two child claude SDK processes back-to-back. Each child runsnpx <pkg>@latestin parallel for stdio MCP servers (chrome-devtools-mcp, server-github, etc). npm tarball extraction isn't safe under concurrent invocations on the same_npxcache directory: the loser silently fails to register its MCP child, leaving the active claude process without (e.g.)chrome-devtools-mcp.Reproduced in two distinct spec-task containers:
ubuntu-external-01kqvsbtsb4nc7pr14dy1zzfk9—--resume <real>claude has nochrome-devtools-mcp, ghost--session-id <new>claude doesubuntu-external-01kpz66jwq1vk8ctzrj054byqq— same pattern (different IDs)PR #46 deduped at the wrapper level (one
claude-agent-acpper(project, agent_id)). PR #47 bumped context_server timeout to 180s. Both still in place. Neither catches the in-wrapper race.What
session_creation_chain: Rc<RefCell<Option<Shared<Task<()>>>>>field onAcpConnectionacquire_session_creation_slot()helper returning(prev_chain, SessionCreationGuard)new_sessionandopen_or_create_session(coversload_sessionandresume_session) take a slot at start, await prev, hold guard until response processedpending_sessionsdedup check, so same-id concurrent calls still share one task (existing behavior preserved)Test
test_concurrent_session_creation_is_serialized:load_sessioncalls dispatched togetherAll 14 existing
agent_serverstests still pass.Caveat
This is a structural defence at the spawn point. The original trigger for two startup
session/newcalls in Zed (one for the saved thread, one for a "ghost" session whose cwd matches the workspace's first worktree path) is still unidentified —agent_panel.rs::load()'s draft branch is ruled out by saved KVP, andset_active→activate_draftis ruled out bybase_viewbeing sync-set before set_active fires. Likely candidates:MultiWorkspace'sActiveWorkspaceChangedflow, orzed.rs::ensure_agent_panel_for_workspace. Tracking as follow-up; this PR prevents the symptom regardless of source.🤖 Generated with Claude Code