fix(hooks): silence MCP-owned-DB augment skip for strict hook runners (#1913)#2134
Conversation
|
@magyargergo is attempting to deploy a commit to the NexusCore Team on Vercel. A member of the Team first needs to authorize it. |
CI Report✅ All checks passed Pipeline Status
Test Results
✅ All 10874 tests passed 16 test(s) skipped — expand for details
Code CoverageTests
📋 View full run · Generated by CI |
a171edc to
456a0e9
Compare
The PreToolUse augment-skip path wrote `[GitNexus] augment skipped: MCP server owns DB` to stderr unconditionally on a normal (non-error) skip. Strict hook runners that validate hook output (e.g. Codex `PreToolUse`) treat that as noisy / "invalid pre-tool-use JSON output". Gate the diagnostic behind GITNEXUS_DEBUG via a shared `isDebugEnabled()` helper, so normal skips are silent by default (empty stdout AND stderr, exit 0) and the reason stays recoverable with `GITNEXUS_DEBUG=1`. Applied consistently to all three hand-maintained hook copies (claude, antigravity, claude-plugin). Tests: - Unit (claude CJS + plugin): assert default-silent and debug-on behavior for the MCP-owned-DB skip and for the fail-closed (lsof ETIMEDOUT) skip that routes through the same gated line; the owner-detection tests run with GITNEXUS_DEBUG=1 so the skip discriminator stays observable. - e2e (antigravity): the antigravity adapter shares the identical gated skip but only runs from its install dir, so cover it through the install pipeline with a faked DB-owner probe (strict empty-stdout/stderr + debug-on). Promote the fake-probe helpers (createHookToolDir / hookEnv, plus a module-private writeExecutable) into shared hook-test-helpers so unit + e2e reuse them. Fixes abhigyanpatwari#1913 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
magyargergo
left a comment
There was a problem hiding this comment.
Tri-review: PR #2134 — fix(hooks): silence MCP-owned-DB augment skip (#1913)
Methods (engine breakdown). 6 Claude reviewer lanes — risk-architect, test/CI (returned partial), correctness, adversarial, maintainability, testing — plus Codex (codex-rescue), the one genuinely independent engine, which ran live and returned structured findings. Two of the three method-families are Claude reading the same diff under different personas, so Claude-only agreement is "consistent across personas," not independent corroboration; only Codex + a Claude lane agreeing is treated as strong. This digest was gated by an independent synthesis-critic before posting.
Verdict: no P0/P1. The change gates a single non-error stderr write behind a shared isDebugEnabled() helper across all three hand-maintained hook copies; the runtime contract (stdout hookSpecificOutput JSON) is untouched. Correctness confirmed the new gate is semantically identical to the old inline check and that no normal hook path still emits ungated output; adversarial could not construct a break (the new tests are non-vacuous — deleting the gate turns stderr.trim()==='' red); risk rated blast radius LOW.
Findings (all P2/P3) — actionable ones already addressed in 456a0e98:
- [P3 · Codex + testing] Antigravity e2e default-silent test used a weaker assertion (
not.toContain/parseHookOutput===null) than the unit suite's strictstderr.trim()===''/stdout.trim()===''. → Fixed: tightened to the strict empty-output checks (re-ran: the install-pipeline owner-skip path is genuinely clean, so the strict assertion holds). - [P2 · risk + testing] The fail-closed (lsof
ETIMEDOUT) skip routes through the same gated line but had only a debug-on test, asymmetric with the MCP-owner path. → Fixed: added a pairedGITNEXUS_DEBUG=''ETIMEDOUT-silent test (CJS + Plugin). - [P2 · maintainability]
writeExecutablewas exported from the shared test helper with no external consumer. → Fixed: made module-private. - [P3 · maintainability]
lbugPathwasn't cleaned in the two new unit tests'finally(the new e2e tests do). → Fixed: added cleanup for consistency.
Considered and deliberately not changed (with rationale):
- [P3 · Codex + risk + correctness] The
main()catch-handler still uses truthyif (process.env.GITNEXUS_DEBUG)while the new helper is strict (=== '1' || === 'true'). All three lanes agree this is pre-existing and not a regression. Left as-is: the catch handler logs genuine exceptions, where a more permissive gate is defensible (you want crash diagnostics even under non-canonical debug values); unifying it to strict would narrow error logging — the wrong direction — and is outside #1913's scope. - GITNEXUS_DEBUG undocumented for the claude/antigravity hooks (P3), and the antigravity
AfterToolstale-index hint writing ungated to stderr (P3, pre-existing #1730, an intentional terminal mirror). Both pre-existing and broader than this fix — noted as follow-ups, not addressed here.
CI. Core code checks green at review time (lint, typecheck, typecheck-web, gitleaks, review, tree-sitter ABI); the full test matrix + image builds were still in flight. Vercel = deploy-auth, not a code failure. 179 unit + e2e tests pass locally; prettier + tsc clean.
Automated multi-tool digest (GitNexus swarm + Compound-Engineering personas + Codex), vetted by a synthesis-critic. Verify before acting — only Codex is an independent engine; the rest share Claude's priors.
The main() catch-handler in all three hook copies still gated its crash log on truthy `if (process.env.GITNEXUS_DEBUG)`, while the skip diagnostic the abhigyanpatwari#1913 fix added is gated on the strict `isDebugEnabled()` helper (=== '1' || === 'true'). That split meant GITNEXUS_DEBUG=0 or =false suppressed the skip line yet still enabled crash logging — two conflicting contract signals in the same file. Switch the three catch handlers to isDebugEnabled() so GITNEXUS_DEBUG has one strict meaning everywhere: exactly '1' or 'true' enables all diagnostics; everything else (incl. '0', 'false', empty, unset) is silent. Add boundary tests asserting the MCP-owner skip stays silent with GITNEXUS_DEBUG='0' and 'false' (CJS + Plugin), pinning the strict contract. Refs abhigyanpatwari#1913 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…DEBUG The antigravity AfterTool handler mirrored the stale-index hint to stderr unconditionally on a normal (non-error) success path — the last ungated stderr write of the class issue abhigyanpatwari#1913 targets, and a divergence from the claude hook, which never mirrors this hint to stderr. Gate the stderr mirror behind isDebugEnabled(). The hint still reaches the agent via additionalContext (stdout JSON) — parts.push(hint) stays unconditional — so there is no functional loss; only the by-default terminal mirror moves behind GITNEXUS_DEBUG=1. This knowingly changes the abhigyanpatwari#1730 terminal-mirror behavior in favor of strict-runner cleanliness and parity with the claude adapter. Split the e2e assertion into a default-silent test (hint in additionalContext, absent from stderr) and a GITNEXUS_DEBUG=1 test (hint mirrored to stderr). Refs abhigyanpatwari#1913 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GITNEXUS_DEBUG was documented only in the cursor integration README, so the diagnostic escape hatch for the Claude Code / Antigravity hooks was undiscoverable. Operators hitting a silent hook skip (MCP server owns the DB, fail-closed probe timeout, or an already-current index) had no documented way to surface the reason. Add a Troubleshooting subsection explaining that the hooks stay silent on normal skip paths for strict runners, that GITNEXUS_DEBUG=1 surfaces the reason on stderr, and that only '1'/'true' enable diagnostics (stdout JSON the agent consumes is unaffected). Refs abhigyanpatwari#1913 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… hint U2 (7995e92) gated the antigravity stale-index hint stderr mirror behind GITNEXUS_DEBUG, but a second test — setup-antigravity.test.ts's "AfterTool emits stale-index hint" — also asserted the hint on stderr by default and was missed (it lives outside the two files validated locally; the full CI matrix caught it). Update it to the U2 contract: assert the hint via additionalContext with stderr silent by default, plus a GITNEXUS_DEBUG=1 run asserting the terminal mirror reappears. Refs abhigyanpatwari#1913 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Fixes #1913. The GitNexus PreToolUse hook's MCP-owned-DB skip path wrote
[GitNexus] augment skipped: MCP server owns DBto stderr unconditionally on a normal (non-error) skip. Strict hook runners that validate hook output — e.g. CodexPreToolUse— treat this as noisy /hook returned invalid pre-tool-use JSON output.This makes the diagnostic debug-only: normal skips are now silent by default (empty stdout and stderr, exit 0), and the reason stays recoverable with
GITNEXUS_DEBUG=1.Root cause
handlePreToolUse→ thehasGitNexusServerOwner(...)branch was the only ungatedprocess.stderr.writeon a normal Pre/PostToolUse path. (The DB-lock probe fails closed — anlsofETIMEDOUT also routes through this same line — so the fail-closed skip is now silent by default too, which is the correct strict-runner trade-off.)The fix
isDebugEnabled()helper (GITNEXUS_DEBUG === '1' || === 'true') and gated the skip diagnostic behind it. Refactored eachextractAugmentContextto reuse the helper (DRY).gitnexus/hooks/claude/gitnexus-hook.cjs(canonical)gitnexus/hooks/antigravity/gitnexus-antigravity-hook.cjsgitnexus-claude-plugin/hooks/gitnexus-hook.jssetup.tsonly rewrites acliPathliteral at install time).Testing
test/unit/hooks.test.ts, claude CJS + plugin): the canonical owner test now asserts default-silent (empty stdout + stderr, exit 0,GITNEXUS_DEBUGforced off for determinism); added a paired debug-on test asserting the message reappears underGITNEXUS_DEBUG=1; the 4 owner-detection tests run withGITNEXUS_DEBUG=1so the skip discriminator stays observable.test/integration/antigravity-hook-e2e.test.ts): the antigravity adapter shares the identical gated skip but only runs from its install dir (its lock/probe helpers are install-time-copied), so it's covered through the install pipeline with a faked DB-owner probe — default-silent + debug-on. The fake-probe helpers (createHookToolDir/hookEnv/writeExecutable) were promoted into sharedhook-test-helpersso unit + e2e reuse them.All 155 unit + 22 antigravity e2e tests pass;
tscclean on the edited files.Review
Self-reviewed via a multi-agent adversarial pass (correctness / completeness / tests / project-standards), each finding independently verified. The only confirmed actionable finding was the missing antigravity behavioral coverage, now added here.
🤖 Generated with Claude Code