[BUG] Project-scoped MCP servers stay bound to the original checkout after EnterWorktree (follow-up to #32220)

Status Open
Reported on v2.1.287
Maintainer reply None cached
Activity 0 comments · opened Oct 1, 2026

Preflight Checklist

  • [x] I'm using the latest version of Claude Code (2.1.287).
  • [x] I searched existing issues. This was reported as #32220, which the inactivity bot closed as not planned and then locked; its closing comment asks for a new issue that references it. Related closed issues: #16089, #25322, #51274, #81688.

What's Wrong?

After EnterWorktree, project-scoped MCP servers declared in .mcp.json stay bound to the instances of the checkout the session was launched from. Tools of a server that is specific to a checkout keep working against the original checkout, and they return wrong results, not errors.

Concrete case: calinburloiu/microtonalist, a Scala project whose dev stack (bin/mtlist-dev-stack, described in docs/agents/dev-stack.md) runs Metals, which serves MCP over HTTP on a port it picks at startup and records in the checkout's .mcp.json. The script resolves everything relative to its own checkout, so a second dev stack started inside the worktree gets its own Metals instance on a different port, recorded in the worktree's .mcp.json. The session's mcp__metals__* tools still call the main checkout's instance:

  • compile-module compiled the main checkout (seen in the main checkout's Metals log), not the worktree.
  • glob-search didn't find a class that exists only in the worktree. The worktree's own instance, called directly over HTTP JSON-RPC, found it.
  • A stdio MCP server whose .mcp.json entry runs a script by absolute path in the main checkout kept reading the main checkout's data, and returned status: ok with the main checkout's numbers.

Subagents with isolation: "worktree" share the parent's MCP connections, so worktrees can't be used to parallelize MCP-dependent work within one session either. The agent can't recover by itself: /mcp is user-only, and it isn't documented which directory's .mcp.json it re-reads after EnterWorktree.

What Should Happen?

In order of preference:

  1. EnterWorktree and ExitWorktree re-resolve project-scoped MCP servers from the new working directory's .mcp.json, reconnecting those whose configuration differs, and the MCP roots (roots/list) follow the worktree (as #32220 also asked for MCP server authors).
  2. The agent can reconnect or reload project-scoped MCP servers against the current working directory, like /mcp does for the user.
  3. At minimum, EnterWorktree warns that project-scoped MCP servers still target the original checkout.

Error Messages/Logs

None. That is the problem: calls succeed against the wrong instance.

Steps to Reproduce

  1. In a git repository, run an HTTP MCP server for the checkout on port A and declare it in .mcp.json: {"mcpServers":{"srv":{"type":"http","url":"http://localhost:A/mcp"}}}. Launch claude there.
  2. Have the agent call EnterWorktree.
  3. In the worktree, start a second instance of the server on port B and write the worktree's .mcp.json with port B.
  4. Have the agent call any mcp__srv__* tool: it reaches port A, the original checkout's instance.

With the linked repository: launch claude in a checkout with bin/mtlist-dev-stack start running, have the agent call EnterWorktree and run bin/mtlist-dev-stack start in the worktree, then compare the metals port in the two .mcp.json files with the instance the mcp__metals__* tools reach (e.g. glob-search for a class added only in the worktree).

Claude Model

Opus

Is this a regression?

I don't know. It was already reported in #32220 (March 2026).

Claude Code Version

2.1.287 (Claude Code)

Operating System

macOS

Additional Information

A related problem in the same kind of session: a user-level PreToolUse hook that rewrites git … into another launcher (rtk git …, from the RTK token-saving proxy) gets every git command refused by the worktree-isolation guard. The guard also refuses any compound command that contains the word "git", even inside heredoc text. Calling /usr/bin/git directly works around it.

🤖 Generated with Claude Code

View original on GitHub ↗