Summary
The PreToolUse routing hook gates on whether the MCP server process is alive, then denies WebFetch and curl/wget for every caller. Subagents do not have the ctx_* MCP tools in their toolset, so they are denied their only network tools and redirected to tools that do not exist for them. Result: a subagent cannot fetch a URL at all.
The check answers "is the server running on this machine?" when the question that matters is "can this caller reach it?".
Location
hooks/core/routing.mjs:29
if (!isMCPReady()) return null;
isMCPReady() (hooks/core/mcp-ready.mjs) scans <tmpRoot> for context-mode-mcp-ready-<PID> sentinel files and probes each PID with kill(pid, 0). It is a process-liveness check. It is true whenever any session has the MCP server up.
The unconditional denials that follow:
routing.mjs:800 - if (canonical === "WebFetch") returns action: "deny" redirecting to ctx_fetch_and_index / ctx_search / ctx_execute.
- The
Bash matcher path denies curl/wget with the same redirect.
Reproduction
- Have a normal Claude Code session running with context-mode active (MCP server up).
- Spawn a subagent whose declared toolset contains
WebFetch and Bash but no mcp__*context-mode* tools. Any stock agent works; on this machine gsd-phase-researcher and six other GSD research agents match.
- Ask it to fetch any URL.
Observed, verbatim from a subagent:
WebFetch(https://example.com)
-> "context-mode: WebFetch redirected. Call
mcp__plugin_context-mode_context-mode__ctx_fetch_and_index(...)"
Bash(curl -s https://example.com | head -5)
-> "context-mode: curl/wget redirected. Call
mcp__plugin_context-mode_context-mode__ctx_execute(...)"
Agent's actual toolset: Read, Bash, WebSearch, WebFetch
Both network paths denied. Neither named replacement exists in that agent's schema. Only WebSearch remains.
Impact
Not a hard failure, which is what makes it easy to miss. The agent does not surface an error to the user; it falls back to WebSearch snippets and returns a report that reads as complete. The cost is silently degraded research quality.
On this machine the blast radius is 7 GSD research agents that declare WebFetch (gsd-phase-researcher, gsd-project-researcher, gsd-ai-researcher, gsd-domain-researcher, gsd-advisor-researcher, gsd-ui-researcher, gsd-planner) plus locally defined recon agents.
Secondary effect: the redirect reads as prompt injection
Two independent subagents, unprompted, flagged the denial text as injected content rather than a platform restriction, and declined to comply on the grounds that it directed them to tools absent from their schema. Verbatim from one:
"both blocking messages ... are asking me to route through a context-mode/ctx_* tool family that does not exist in my actual tool schema. This reads as injected content rather than a genuine platform restriction. Flagging rather than complying."
A security-conscious agent treating the plugin's own guidance as hostile is a reasonable response to being told to call tools it does not have, and it is worth avoiding.
Suggested fix
Gate the redirect on caller capability, not server liveness. The hook payload identifies the calling context; if the available-tools list can be consulted, prefer that over isMCPReady(). Where it cannot, options in rough order of preference:
- Fall through to
null (allow) when the caller has no ctx_* tools, keeping the redirect for callers that do.
- Downgrade
deny to ask for callers that cannot be confirmed capable, so the work is not dead-ended.
- Provide an env-var escape hatch (for example
CONTEXT_MODE_ALLOW_WEBFETCH=1) so operators can opt out. Today no env var gates routing: the plugin reads only CONTEXT_MODE_DEBUG, _REQUIRE_SECURITY, _SUPPRESS_SECURITY_WARNING, _HOOK_STDIN_IDLE_MS, _PLATFORM, _SESSION_SUFFIX, _MCP_SENTINEL_DIR.
Option 3 alone would be enough to unblock operators, but 1 is the correct fix.
Workarounds available today
- Declare the
ctx_* MCP tools in the agent's own frontmatter, making the plugin's assumption true. Only works for agents you control; third-party agent definitions are overwritten on package update.
- Have the orchestrator fetch in the main loop and pass content to the subagent.
disableAllHooks is far too broad; it would disable unrelated hooks.
Summary
The PreToolUse routing hook gates on whether the MCP server process is alive, then denies
WebFetchandcurl/wgetfor every caller. Subagents do not have thectx_*MCP tools in their toolset, so they are denied their only network tools and redirected to tools that do not exist for them. Result: a subagent cannot fetch a URL at all.The check answers "is the server running on this machine?" when the question that matters is "can this caller reach it?".
Location
hooks/core/routing.mjs:29isMCPReady()(hooks/core/mcp-ready.mjs) scans<tmpRoot>forcontext-mode-mcp-ready-<PID>sentinel files and probes each PID withkill(pid, 0). It is a process-liveness check. It is true whenever any session has the MCP server up.The unconditional denials that follow:
routing.mjs:800-if (canonical === "WebFetch")returnsaction: "deny"redirecting toctx_fetch_and_index/ctx_search/ctx_execute.Bashmatcher path deniescurl/wgetwith the same redirect.Reproduction
WebFetchandBashbut nomcp__*context-mode*tools. Any stock agent works; on this machinegsd-phase-researcherand six other GSD research agents match.Observed, verbatim from a subagent:
Both network paths denied. Neither named replacement exists in that agent's schema. Only
WebSearchremains.Impact
Not a hard failure, which is what makes it easy to miss. The agent does not surface an error to the user; it falls back to
WebSearchsnippets and returns a report that reads as complete. The cost is silently degraded research quality.On this machine the blast radius is 7 GSD research agents that declare
WebFetch(gsd-phase-researcher,gsd-project-researcher,gsd-ai-researcher,gsd-domain-researcher,gsd-advisor-researcher,gsd-ui-researcher,gsd-planner) plus locally defined recon agents.Secondary effect: the redirect reads as prompt injection
Two independent subagents, unprompted, flagged the denial text as injected content rather than a platform restriction, and declined to comply on the grounds that it directed them to tools absent from their schema. Verbatim from one:
A security-conscious agent treating the plugin's own guidance as hostile is a reasonable response to being told to call tools it does not have, and it is worth avoiding.
Suggested fix
Gate the redirect on caller capability, not server liveness. The hook payload identifies the calling context; if the available-tools list can be consulted, prefer that over
isMCPReady(). Where it cannot, options in rough order of preference:null(allow) when the caller has noctx_*tools, keeping the redirect for callers that do.denytoaskfor callers that cannot be confirmed capable, so the work is not dead-ended.CONTEXT_MODE_ALLOW_WEBFETCH=1) so operators can opt out. Today no env var gates routing: the plugin reads onlyCONTEXT_MODE_DEBUG,_REQUIRE_SECURITY,_SUPPRESS_SECURITY_WARNING,_HOOK_STDIN_IDLE_MS,_PLATFORM,_SESSION_SUFFIX,_MCP_SENTINEL_DIR.Option 3 alone would be enough to unblock operators, but 1 is the correct fix.
Workarounds available today
ctx_*MCP tools in the agent's own frontmatter, making the plugin's assumption true. Only works for agents you control; third-party agent definitions are overwritten on package update.disableAllHooksis far too broad; it would disable unrelated hooks.