mirror of https://github.com/garrytan/gstack.git
fix(team-init): emit PreToolUse deny in hookSpecificOutput shape
The check-gstack.sh hook generated by gstack-team-init printed permissionDecision at the top level. Claude Code's PreToolUse decision control only reads hookSpecificOutput.permissionDecision (with hookEventName), so the deny was silently ignored and the required-mode gate never actually blocked skill usage when gstack was missing. Use the same hookSpecificOutput shape adopted for careful/freeze in #1509, and add a test that executes the generated hook in both scenarios (gstack missing -> structured deny; installed -> {}).
This commit is contained in:
parent
a5833c413f
commit
6393079468
|
|
@ -127,7 +127,7 @@ Install it:
|
|||
|
||||
Then restart your AI coding tool.
|
||||
MSG
|
||||
echo '{"permissionDecision":"deny","message":"gstack is required but not installed. See stderr for install instructions."}'
|
||||
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"gstack is required but not installed. See stderr for install instructions."}}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,29 @@ describe('gstack-team-init', () => {
|
|||
expect(stat.mode & 0o111).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('required: hook emits PreToolUse deny in hookSpecificOutput shape when gstack is missing', () => {
|
||||
run(`${TEAM_INIT} required`, { cwd: tmpDir });
|
||||
const hookPath = path.join(tmpDir, '.claude', 'hooks', 'check-gstack.sh');
|
||||
|
||||
// HOME without gstack → must deny via hookSpecificOutput (top-level
|
||||
// permissionDecision is ignored by Claude Code's PreToolUse schema)
|
||||
const fakeHome = path.join(tmpDir, 'fake-home');
|
||||
fs.mkdirSync(fakeHome, { recursive: true });
|
||||
const denied = run(`bash ${hookPath}`, { env: { HOME: fakeHome } });
|
||||
expect(denied.exitCode).toBe(0);
|
||||
const deniedJson = JSON.parse(denied.stdout.trim());
|
||||
expect(deniedJson.hookSpecificOutput.hookEventName).toBe('PreToolUse');
|
||||
expect(deniedJson.hookSpecificOutput.permissionDecision).toBe('deny');
|
||||
expect(deniedJson.hookSpecificOutput.permissionDecisionReason).toContain('gstack is required');
|
||||
|
||||
// HOME with gstack installed → no-op {}
|
||||
const homeWithGstack = path.join(tmpDir, 'home-with-gstack');
|
||||
fs.mkdirSync(path.join(homeWithGstack, '.claude', 'skills', 'gstack', 'bin'), { recursive: true });
|
||||
const allowed = run(`bash ${hookPath}`, { env: { HOME: homeWithGstack } });
|
||||
expect(allowed.exitCode).toBe(0);
|
||||
expect(JSON.parse(allowed.stdout.trim())).toEqual({});
|
||||
});
|
||||
|
||||
test('required: creates project settings.json with PreToolUse hook', () => {
|
||||
run(`${TEAM_INIT} required`, { cwd: tmpDir });
|
||||
const settingsPath = path.join(tmpDir, '.claude', 'settings.json');
|
||||
|
|
|
|||
Loading…
Reference in New Issue