You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The MCP-readiness sentinel guard introduced in 04569b1 (fix for #230) is silently bypassed in normal Claude Code sessions because the MCP server and the hook process resolve process.ppid to different PIDs. As a result, isMCPReady() always returns false for hooks, and mcpRedirect() swallows every deny/modify decision — WebFetch, curl/wget, inline-HTTP, and gradle/maven redirects all become silent passthroughs.
Effectively, the core routing policy of context-mode is no-op for any Claude Code installation that spawns hooks via a shell wrapper.
Root cause
src/server.ts (MCP server) writes the sentinel using its ownprocess.ppid:
// src/server.ts (introduced by #230)constmcpSentinel=join(tmpdir(),`context-mode-mcp-ready-${process.ppid}`);// ...writeFileSync(mcpSentinel,String(process.pid));
hooks/core/mcp-ready.mjs reads the sentinel using its ownprocess.ppid:
This only works if both processes share the same parent. They don't:
Process
How spawned
process.ppid
MCP server
Claude Code → node start.mjs (direct)
Claude Code main PID
Hook (pretooluse.mjs)
Claude Code → bash -c \"node hooks/pretooluse.mjs\"
Transient bash PID
So the sentinel exists at /tmp/context-mode-mcp-ready-${CC_PID}, but the hook looks for /tmp/context-mode-mcp-ready-${TRANSIENT_BASH_PID} — different file, never found.
Reproduction
Environment:
context-mode v1.0.94 (installed via Claude Code marketplace, autoUpdate: true)
Claude Code CLI on Linux (WSL2, Ubuntu)
Steps:
Start a Claude Code session with context-mode plugin enabled.
Verify the MCP server is alive and the sentinel exists:
ls /tmp/context-mode-mcp-ready-*
# → /tmp/context-mode-mcp-ready-<CC_PID> (PID inside is alive)
Trigger any redirect from agent (e.g. ask agent to call WebFetch).
Observed: WebFetch succeeds. No deny shown.
Expected: deny with permissionDecision: \"deny\" and the Use ctx_fetch_and_index ... reason.
All of these become silent no-ops, so the agent quietly uses original tools and floods the context window — the exact thing context-mode is designed to prevent.
ctx_doctor reports green across the board because it only checks file existence, not behavior.
Suggested fixes
Any of these would resolve the PPID mismatch. Listed roughly in increasing invasiveness:
Directory-scan + PID liveness probe — drop the per-PPID filename. MCP server writes a fixed-path sentinel ${tmpdir}/context-mode-mcp-ready/<server-pid>. Hook scans the directory and returns true if any file's PID is alive (process.kill(pid, 0)).
Lockfile + stat — server holds an exclusive lockfile at a fixed path; hook checks lock holder's PID liveness. Same idea, OS-level primitive.
Environment-variable handshake — MCP server export CTX_MODE_MCP_PID=<pid> into the session somehow (or write a dotenv next to plugin.json that hooks source). Less portable.
Walk up the process tree — hook walks up its parent chain (/proc/<pid>/status PPID lines on Linux, ps -o ppid= elsewhere) until it finds a PID that has a matching sentinel file. Works on any spawn topology.
Option 1 looks cleanest and avoids the cross-platform PPID resolution rabbit hole.
Workarounds
For users who want the redirects back today: pin to a version prior to 04569b1 (2026-04-13) and disable marketplace autoUpdate. The mcpRedirect guard is the only thing producing the regression — earlier versions called the redirect formatter directly.
Notes
Reproduced under Claude Code on Linux (WSL2, Ubuntu). Other Claude Code platforms that wrap hook commands in bash -c \"...\" will behave the same. Direct-spawn platforms (e.g. some Cursor/VSCode adapters) may coincidentally work because their hook spawn topology happens to share a PPID with the MCP server.
Summary
The MCP-readiness sentinel guard introduced in
04569b1(fix for #230) is silently bypassed in normal Claude Code sessions because the MCP server and the hook process resolveprocess.ppidto different PIDs. As a result,isMCPReady()always returnsfalsefor hooks, andmcpRedirect()swallows every deny/modify decision —WebFetch,curl/wget, inline-HTTP, and gradle/maven redirects all become silent passthroughs.Effectively, the core routing policy of context-mode is no-op for any Claude Code installation that spawns hooks via a shell wrapper.
Root cause
src/server.ts(MCP server) writes the sentinel using its ownprocess.ppid:hooks/core/mcp-ready.mjsreads the sentinel using its ownprocess.ppid:This only works if both processes share the same parent. They don't:
process.ppidnode start.mjs(direct)pretooluse.mjs)bash -c \"node hooks/pretooluse.mjs\"So the sentinel exists at
/tmp/context-mode-mcp-ready-${CC_PID}, but the hook looks for/tmp/context-mode-mcp-ready-${TRANSIENT_BASH_PID}— different file, never found.Reproduction
Environment:
v1.0.94(installed via Claude Code marketplace,autoUpdate: true)Steps:
WebFetch).WebFetchsucceeds. No deny shown.permissionDecision: \"deny\"and theUse ctx_fetch_and_index ...reason.Direct verification of the guard:
Output (run from a child of Claude Code):
While the actual sentinel sits at
/tmp/context-mode-mcp-ready-1034965(Claude Code's main PID), pointing to the live MCP server PID1035016.End-to-end confirmation — manually invoking the hook with a
WebFetchpayload returns empty stdout (passthrough):And the routing function directly:
Impact (all affected by the same swallow)
WebFetchdeny (routing.mjs:313-320)curl/wgetredirect-to-execute (routing.mjs:255-263)fetch(...),requests.get,http.get) redirect (routing.mjs:274-285)routing.mjs:289-297)All of these become silent no-ops, so the agent quietly uses original tools and floods the context window — the exact thing context-mode is designed to prevent.
ctx_doctorreports green across the board because it only checks file existence, not behavior.Suggested fixes
Any of these would resolve the PPID mismatch. Listed roughly in increasing invasiveness:
${tmpdir}/context-mode-mcp-ready/<server-pid>. Hook scans the directory and returns true if any file's PID is alive (process.kill(pid, 0)).CTX_MODE_MCP_PID=<pid>into the session somehow (or write a dotenv next to plugin.json that hookssource). Less portable./proc/<pid>/statusPPID lines on Linux,ps -o ppid=elsewhere) until it finds a PID that has a matching sentinel file. Works on any spawn topology.Option 1 looks cleanest and avoids the cross-platform PPID resolution rabbit hole.
Workarounds
For users who want the redirects back today: pin to a version prior to
04569b1(2026-04-13) and disable marketplaceautoUpdate. ThemcpRedirectguard is the only thing producing the regression — earlier versions called the redirect formatter directly.Notes
bash -c \"...\"will behave the same. Direct-spawn platforms (e.g. some Cursor/VSCode adapters) may coincidentally work because their hook spawn topology happens to share a PPID with the MCP server.