fix(hooks): unify GITNEXUS_DEBUG gating in main() catch handlers

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 #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 #1913

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergo Magyar 2026-06-10 06:56:15 +00:00
parent 456a0e9899
commit b65276146f
4 changed files with 50 additions and 3 deletions

View file

@ -381,7 +381,7 @@ function main() {
const handler = handlers[input.hook_event_name || ''];
if (handler) handler(input);
} catch (err) {
if (process.env.GITNEXUS_DEBUG) {
if (isDebugEnabled()) {
console.error('GitNexus hook error:', (err.message || '').slice(0, 200));
}
}

View file

@ -352,7 +352,7 @@ function main() {
const handler = handlers[input.hook_event_name || ''];
if (handler) handler(input);
} catch (err) {
if (process.env.GITNEXUS_DEBUG) {
if (isDebugEnabled()) {
console.error('GitNexus antigravity hook error:', (err.message || '').slice(0, 200));
}
}

View file

@ -376,7 +376,7 @@ function main() {
const handler = handlers[input.hook_event_name || ''];
if (handler) handler(input);
} catch (err) {
if (process.env.GITNEXUS_DEBUG) {
if (isDebugEnabled()) {
console.error('GitNexus hook error:', (err.message || '').slice(0, 200));
}
}

View file

@ -1005,6 +1005,53 @@ describe('PreToolUse augmentation filtering (integration)', () => {
}
},
);
// #1913: the GITNEXUS_DEBUG contract is strict — ONLY '1' and 'true' enable
// diagnostics. Pin that non-canonical truthy-looking values ('0', 'false')
// are treated as OFF, so the skip stays silent. A truthy-gated reader would
// have emitted on these; this guards the unified strict gate (incl. the
// main() catch handler) across the claude/plugin copies.
for (const debugValue of ['0', 'false']) {
it.skipIf(process.platform === 'win32')(
`${label}: MCP-owner skip stays SILENT with GITNEXUS_DEBUG='${debugValue}' (strict contract)`,
() => {
const markerPath = path.join(
os.tmpdir(),
`gitnexus-hook-dbg-${debugValue}-${process.pid}-${label}`,
);
const lbugPath = path.join(gitNexusDir, 'lbug');
fs.writeFileSync(lbugPath, '');
fs.rmSync(markerPath, { force: true });
const binDir = createHookToolDir({
gitnexusMarkerPath: markerPath,
lsofOutput: '12345\n',
psOutput: 'node /tmp/node_modules/.bin/gitnexus mcp\n',
});
try {
const result = runHook(
hookPath,
{
hook_event_name: 'PreToolUse',
tool_name: 'Grep',
tool_input: { pattern: 'validateUser' },
cwd: tmpDir,
},
undefined,
{ env: { ...hookEnv(binDir), GITNEXUS_DEBUG: debugValue } },
);
expect(result.stdout.trim()).toBe('');
expect(result.stderr.trim()).toBe('');
expect(result.status).toBe(0);
expect(fs.existsSync(markerPath)).toBe(false);
} finally {
fs.rmSync(lbugPath, { force: true });
fs.rmSync(markerPath, { force: true });
fs.rmSync(binDir, { recursive: true, force: true });
}
},
);
}
}
});