fix: hooks gate on infigraph-mcp liveness before pointing at unreachable tools#35
Open
pradeepmouli wants to merge 2 commits into
Open
fix: hooks gate on infigraph-mcp liveness before pointing at unreachable tools#35pradeepmouli wants to merge 2 commits into
pradeepmouli wants to merge 2 commits into
Conversation
…ble tools Three shipped hooks unconditionally instruct Claude to call an mcp__infigraph__* tool with no fallback if that tool is unreachable (MCP disconnected, e.g. via the worker's own idle-self-termination): - The enforcement hook denies raw Grep/Glob/Bash/Read/Write/Agent calls and points at an MCP tool as the required alternative. Its only escape hatch (the search-fallback sentinel) can never fire in this case, since it's only ever set AFTER a real tool call has already succeeded once -- meaning a disconnected MCP server blocks every raw tool forever with no path out at all. - The session-save reminder unconditionally tells Claude to call save_session, causing full narrative compositions to be wasted on calls that were guaranteed to fail. - The session-start hook's compaction/clear-recovery paths have the same problem for both save_session and get_latest_session. A hook script can't query Claude Code's actual MCP connection state (not exposed to hooks), so `pgrep -f infigraph-mcp` is used as a cheap, necessarily one-sided heuristic: no process found is high-confidence the tool call would fail, but a process found is NOT proof it's reachable from this session (could be stale, orphaned, or serving a different session) -- pgrep can only rule the tool OUT, never confirm it's IN. Given that asymmetry, the two write-side hooks (session-save, session-start) keep pgrep as a framing hint only -- the actual gate against wasted composition is always a cheap availability check done before writing any content, regardless of what pgrep found, with an embedded fallback recipe describing the exact on-disk SessionStore schema so the write survives even with MCP down. The enforcement hook's "no process at all" case is a real bypass instead (exit 0), since blocking with no recovery path is worse than occasionally allowing a raw tool when it wasn't strictly necessary; every denial reason also now tells Claude how to self-serve the existing sentinel if the suggested tool turns out unreachable despite a process being found, so retrying doesn't just hit the same block again. get_latest_session gets no fallback recipe at all -- unlike a write, a read needs no new content composed, and the hook already reads .infigraph/sessions/*.json directly for its own startup/resume context injection, so that's surfaced up front regardless of tool reachability. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TAXDJyFdnA4BV1U2fxufdC
pradeepmouli
requested review from
johnintuit,
murari316 and
sandeep-mewara
as code owners
July 23, 2026 20:38
…s counter SendMessage-delivered turns (idle notifications, status pings from background agents) fire UserPromptSubmit just like real human input, so concurrent subagents were inflating the save-reminder counter far faster than actual user exchanges -- one live session saw 183 fires from ~20 real turns. Skip the counter entirely when the prompt is a <teammate-message> relay, so it tracks what its own doc comment already claims: user exchanges, not agent-orchestration chatter.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three of the shipped Claude Code hooks unconditionally instruct the agent to call an
mcp__infigraph__*tool, with no fallback if that tool happens to be unreachable (MCP disconnected — e.g. via the worker's own idle-self-termination,I-5/R2.2.3):ENFORCE_HOOK_SCRIPTdenies rawGrep/Glob/Bash/Read/Write/Agentcalls and points at an MCP tool as the required alternative. Its only escape hatch (the search-fallback sentinel) can never fire in this case, since it's only ever set after a real tool call has already succeeded once — meaning a disconnected MCP server blocks every raw tool forever with no path out at all.SESSION_SAVE_HOOK_SCRIPTunconditionally tells the agent to callsave_session, causing full narrative compositions to be discarded on calls that were guaranteed to fail.SESSION_START_HOOK_SCRIPT'scompact/clear-recovery paths have the same problem for bothsave_sessionandget_latest_session.A hook script can't query Claude Code's actual MCP connection state (not exposed to hooks), so
pgrep -f infigraph-mcpis used as a cheap, necessarily one-sided heuristic: no process found is high-confidence the tool call would fail, but a process found is not proof it's reachable from this specific session (could be stale, orphaned, or serving a different session) — pgrep can only rule the tool out, never confirm it's in.Given that asymmetry:
session-save,session-start) keep pgrep as a framing hint only — the actual gate against wasted composition is always a cheap availability check done before writing any content, regardless of what pgrep found, with an embedded fallback recipe describing the exact on-diskSessionStoreschema so the write survives even with MCP down.exit 0), since blocking with zero recovery path is worse than occasionally allowing a raw tool when it wasn't strictly necessary. Every denial reason also now tells the agent how to self-serve the existing sentinel if the suggested tool turns out unreachable despite a process being found, so retrying doesn't just hit the same block again.get_latest_sessiongets no fallback recipe at all — unlike a write, a read needs no new content composed, and the hook already reads.infigraph/sessions/*.jsondirectly for its own startup/resume context injection, so that's surfaced up front regardless of tool reachability.Scope
Deliberately narrow — only these three hooks. The remaining shipped hooks either can't fire while MCP is down at all (
PostToolUsehooks gated on a specific MCP tool call having already succeeded:SESSION_RESET,TEST_CONTEXT_SENTINEL,SEARCH_FALLBACK_SENTINEL), or have no MCP dependency to begin with (SESSION_END_SAVEis already transcript/file-based,EDIT_TRACKER,CLEAR_SUGGEST).Test plan
cargo test -p infigraph-cli hooks::— 14/14 pass unchanged (existing tests assert on stable substrings, e.g."deny-by-default","Grep)", none of which moved)cargo fmt -p infigraph-cli -- --checkcleancargo clippy -p infigraph-cli --all-targets -- -D warningscleanbash -nsyntax verification after extraction.search-fallback-allowedsentinel is a genuine global bypass across every tool typeENFORCE_HOOK_SCRIPTcovers (not scoped to search specifically), confirming the self-serve recovery instruction is mechanically correct--ignoredperf suites) ran clean on the actual commit, not bypassed🤖 Generated with Claude Code
https://claude.ai/code/session_01TAXDJyFdnA4BV1U2fxufdC