MCP `.mcp.json` project-trust modal re-fires on every launch in Linux docker container (v2.1.141 and v2.1.152)
Summary
Claude Code's project-trust modal for .mcp.json re-fires on every launch when claude runs inside a Linux docker container, even when the user previously approved the server and the OAuth flow is complete and the server connects successfully on every launch. The same project, same .mcp.json, same claude version run natively on the macOS host does not reproduce. Confirmed in both v2.1.141 and v2.1.152.
Environment
- Claude Code v2.1.141 and v2.1.152 (both reproduce)
- Host: macOS (arm64), Docker Desktop
- Container: Linux (arm64), claude CLI bundled into a custom image, launched via
docker run --rm --init --network=host -it ... my-image:latest claude --permission-mode acceptEdits --effort high - Inside container:
HOME=/data,CLAUDE_CONFIG_DIR=/data(flat layout, no.claude/segment between HOME and the user-tier files) - Project root bind-mounted identity (host path == container path)
/databind-mounted from a host directory for persistent state- Project
.mcp.json(minimal):
``json``
{
"mcpServers": {
"my-mcp": {
"type": "http",
"url": "http://localhost:8080/mcp"
}
}
}
Reproduction
- In a project containing
.mcp.jsonwith at least one project-scoped MCP server, launch claude inside the Linux container. - Modal appears: "New MCP server found in this project: my-mcp" (text in 2.1.152; was "in .mcp.json" in 2.1.141), with options:
- Use this MCP server
- Use this and all future MCP servers in this project
- Continue without using this MCP server
- Select option 1 (or 2), Enter, see main TUI.
- Run
/mcpand complete OAuth for the server. Auth tokens land in$CLAUDE_CONFIG_DIR/.credentials.jsonundermcpOAuth. Server is connectable. /quit, re-launch the container.- Modal appears again. Repeat N times — fires on every single launch.
Expected Behavior
After selecting option 1 or 2, the modal should not appear again unless the .mcp.json declaration changes.
Actual Behavior
Modal appears on every launch indefinitely. State writes to <project>/.claude/settings.local.json do happen but are not honored on the next launch's suppression check. The persistent re-prompt makes the project-trust approval flow effectively unusable in containerized launches.
Evidence from --debug 'mcp,settings'
Approval is written:
[DEBUG] Writing to temp file: <project>/.claude/settings.local.json.tmp.<N>.<hex>
[DEBUG] Renaming ... to <project>/.claude/settings.local.json
[DEBUG] File <project>/.claude/settings.local.json written atomically
Resulting file content after approval:
{
"enableAllProjectMcpServers": true,
"enabledMcpjsonServers": ["my-mcp"],
"disabledMcpjsonServers": []
}
On every subsequent launch the server connects successfully:
[DEBUG] MCP server "my-mcp": Successfully connected (transport: http) in 107ms
[DEBUG] Connection established with capabilities: {"hasTools":true,"hasPrompts":true,"hasResources":true,"hasResourceSubscribe":false,"serverVersion":{"name":"my-mcp","version":"1.0.0"}}
...yet the modal still appears at TUI render time.
Things that do NOT suppress the modal
enabledMcpjsonServers: ["<name>"]in<project>/.claude/settings.local.jsonenableAllProjectMcpServers: truein same- The same fields in
~/.claude.jsonprojects[<projectRoot>]entry - Combinations of both
--setting-sources user,project,local--mcp-config <path>(with or without--strict-mcp-config)- Pre-registering the same server user-scoped at
~/.claude.jsontop-levelmcpServers(server still connects via that registration, but project-scoped modal still fires) - Completed OAuth (tokens in
.credentials.json, server connects on every launch)
Does NOT reproduce on the macOS host
The same .mcp.json in the same project, run with the same claude version installed natively from the same npm package, does not show the modal at all — on any launch — under any of these host configurations:
- Default
~/.claude/config dir, accumulated user state from many launches - Fresh
CLAUDE_CONFIG_DIR=/tmp/fresh-claude(empty state) HOME=/tmp/sim CLAUDE_CONFIG_DIR=/tmp/sim(matching the container's flat-layout env)
So the trigger is environment-specific to the Linux docker code path — not state-driven, and not driven by the flat-layout HOME==CLAUDE_CONFIG_DIR semantics by themselves.
Asks
- Identify which condition in the Linux containerized launch causes the modal-suppression check to fail when
settings.local.jsonshould suppress it. - Fix so that approval state in
settings.local.jsonis honored on Linux containerized launches. - Ideally: log a debug line when the modal trigger fires, naming which suppression check failed. Would have saved hours of investigation.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗