C:/Program Files/Git/mcp settings menu refuses to open in genuinely interactive terminal sessions — client-attachment check inherits the $CLAUDE_JOB_DIR-as-background-proxy defect from #59848

Status Open
Reported on v2.1.139
Maintainer reply None cached
Activity 0 comments · opened Jul 29, 2026

Summary

On Windows, every claude session — including ones with a real terminal actively attached and a user typing — is permanently tagged as a background/"spare" session in the daemon's own bookkeeping (roster.json: "source": "spare", no subsequent "claimed"/interactive transition ever recorded). This causes /mcp to refuse to open with "Can't open MCP settings in a background session," even while the user is mid-conversation in that exact session.

This is the same root defect identified and (partially) fixed in #59848 ("Interactive claude sessions are classified as background jobs post-2.1.139") — but that fix addressed only the worktree bgIsolation guard consumer. The /mcp gating logic added afterward (2.1.214, 2.1.216) checks "is a terminal/client attached" using the same unfixed signal, so it inherits the identical false positive.

Environment

  • Claude Code CLI version: 2.1.220 (also reproduced on 2.1.212 before an in-session auto-update)
  • OS: Windows 11 Pro (10.0.26200)
  • Shell: PowerShell 7, direct claude invocation, no wrapper/alias/launcher script
  • Daemon: transient, singleton per machine (~/.claude/daemon.json, ~/.claude/daemon.log, ~/.claude/daemon/roster.json)

Repro

  1. Open PowerShell, cd into any project directory.
  2. Run claude (no flags).
  3. Start a normal interactive conversation.
  4. Run /mcp.
  5. Observe: "Can't open MCP settings in a background session — use /mcp enable|disable|reconnect <server> to steer, or run /mcp from an interactive terminal to authenticate." — despite this being exactly that interactive terminal.

Reproduces on a brand-new terminal + brand-new session every time, not just resumed/continued sessions.

Evidence

Process tree (Get-CimInstance Win32_Process) for the session in question:

claude.exe --bg-pty-host \\.\pipe\cc-daemon-<id>-pty-<session> <cols> <rows> -- claude.exe --session-id <uuid> --agent claude
  └─ claude.exe --session-id <uuid> --agent claude     (the actual interactive engine)
       └─ node.exe  <plugin mcp servers...>

Every session — even the very first one spawned right after a cold daemon start — is created via this --bg-pty-host path, not a standalone foreground process.

~/.claude/daemon/roster.json — every worker entry, regardless of how long the user has been actively typing into it, permanently carries:

"dispatch": {
  "source": "spare",
  ...
}

No field anywhere in the schema records a "claimed by interactive client" transition. This is confirmed on sessions that had 20+ minutes of continuous interactive use at time of inspection.

~/.claude/daemon.log — representative sequence:

[supervisor] ─── daemon start ─── version=2.1.220 pid=29728 origin=transient
[bg] bg: control socket bound at \\.\pipe\cc-daemon-*-control
[bg] bg spawned 039ad304 (spare)      <- first session on a cold daemon, still "(spare)"

Killing the daemon entirely (claude daemon stop --any) does not help: a fresh daemon starts on the next claude invocation and immediately spawns the new session via the identical (spare) path. There is no remaining non-daemon/foreground launch code path to fall back to.

Config/settings ruled out — confirmed clean, not the cause:

  • No relevant key in ~/.claude/settings.json or ~/.claude/settings.local.json (global or project-level).
  • No shell alias/wrapper shadowing claude.exe on PATH.
  • No relevant env var (CLAUDE_CODE_AUTO_BACKGROUND_TIMEOUT_MS set/unset made no difference).
  • No flag in claude --help / claude daemon --help to disable the daemon or spare pool.

Changelog timeline (CHANGELOG.md, newest-first):

  • 2.1.208: "/install-github-app and the /mcp settings menu no longer open in background sessions" (blanket block introduced)
  • 2.1.214: "Fixed /install-github-app and the /mcp settings menu being blocked in agent-view sessions — they're now refused only in background sessions with no terminal attached" (intent: allow when a terminal is attached)
  • 2.1.216: "Background sessions: /mcp and /install-github-app now park a 'needs input' request in the agent view when no client is attached" (further refinement, same intent)

By this stated intent, /mcp should work in the reproduction above, since a terminal is genuinely attached. It doesn't — because the "is a client attached" check appears to read the same source/template signal that #59848 already showed is set unconditionally by the daemon's spare-pool registration, not by actual attachment state.

Relationship to #59848

#59848 (closed 2026-05-27) diagnosed the general mechanism: since the warm-spare pool became "real" around v2.1.139, the daemon registers every session (not just genuine background jobs) and sets $CLAUDE_JOB_DIR unconditionally, which downstream consumers wrongly treat as "this is a background job." The maintainer's fix ("interactive sessions are no longer misclassified as background jobs") closed that issue, but — based on the roster.json evidence above, gathered fresh on 2.1.220 — the underlying signal defect is still present at the daemon level. It appears only the specific consumer raised in #59848 (the worktree bgIsolation guard) was patched; the /mcp//install-github-app gating logic added in the same era (2.1.208–2.1.216) was built on the same unfixed signal and never got an equivalent patch, or regressed independently.

Per #59848's own analysis (commenter kcarriedo), "is this background" collapses at least four orthogonal axes (TTY-attachment, spawn origin, persistence, isolation policy) into one signal. The /mcp gate needs axis 1 (TTY-attachment) specifically — the daemon needs to expose that as its own signal rather than deriving it from spare-pool registration state.

Suggested fix direction

Expose actual TTY/client-attachment as an independent, first-class signal in the daemon's worker bookkeeping (e.g., a clientAttached: bool or attachedAt: timestamp field in roster.json, flipped on real socket/PTY attach and cleared on detach) and have the /mcp//install-github-app gate (and any other "is this background" consumer) check that signal directly, instead of source/template/$CLAUDE_JOB_DIR.

Impact

Any Windows user launching claude directly from a terminal cannot use /mcp's interactive settings/auth flow at all, on any session, regardless of how long they've been actively working in it. Confirmed workaround: the desktop app is unaffected (does not go through this daemon/pty-host path).

View original on GitHub ↗