test(gateguard): fail the allow-cases when the hook returns nothing

The "does not gate as destructive" cases guarded the decision behind
`if (output && output.hookSpecificOutput)`, so a crashed or silent hook made
parseOutput return null and the test passed having asserted nothing.

Assert the exit code and that output parsed first, then branch: a decision
object must not be a Destructive deny, and pass-through must echo the input
back — the same shape the existing retry case already checks.

Raised in review on #2829.
This commit is contained in:
Nguyen Thanh Dat
2026-09-07 15:48:02 +07:00
parent 79d3fefad1
commit 249b0ecf7f
+12 -3
View File
@@ -315,13 +315,22 @@ function runTests() {
// be mistaken for a destructive denial.
runBashHook({ tool_name: 'Bash', tool_input: { command: 'printf ready' } });
const result = runBashHook({ tool_name: 'Bash', tool_input: { command } });
// Assert the hook actually answered before reading the decision: a
// crashed or silent hook makes parseOutput return null, and a bare
// `if (output)` would let this case pass without testing anything.
assert.strictEqual(result.code, 0, `hook should exit 0 for ${command}`);
const output = parseOutput(result.stdout);
if (output && output.hookSpecificOutput) {
const reason = output.hookSpecificOutput.permissionDecisionReason || '';
assert.ok(output, `hook should produce JSON output for ${command}`);
const decision = output.hookSpecificOutput;
if (decision) {
const reason = decision.permissionDecisionReason || '';
assert.ok(
output.hookSpecificOutput.permissionDecision !== 'deny' || !reason.includes('Destructive'),
decision.permissionDecision !== 'deny' || !reason.includes('Destructive'),
`${command} must not be gated as destructive`
);
} else {
// Pass-through echoes the input back unchanged.
assert.strictEqual(output.tool_name, 'Bash', 'pass-through should preserve input');
}
})
)