diff --git a/bin/gstack-team-init b/bin/gstack-team-init index 6b9cadf42..2c92c82b4 100755 --- a/bin/gstack-team-init +++ b/bin/gstack-team-init @@ -151,10 +151,10 @@ Install it: Then restart your AI coding tool. MSG - # Claude Code reads hookSpecificOutput; Copilot CLI reads the top-level - # fields (#2229). Exit 0 so both parse the decision instead of an error. - echo '{"permissionDecision":"deny","permissionDecisionReason":"gstack is required but not installed. See stderr for install instructions.","hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"gstack is required but not installed. See stderr for install instructions."}}' - exit 0 + # Exit 2 denies on Claude Code (#2413) and on Copilot CLI, which treats a + # preToolUse exit 2 as deny (#2229). + echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"gstack is required but not installed. See stderr for install instructions."}}' + exit 2 fi echo '{}' diff --git a/test/team-mode.test.ts b/test/team-mode.test.ts index e7a305c9b..f8c267c3b 100644 --- a/test/team-mode.test.ts +++ b/test/team-mode.test.ts @@ -2,7 +2,7 @@ import { describe, test, expect, beforeEach, afterEach } from 'bun:test'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; -import { execSync, spawnSync } from 'child_process'; +import { execSync } from 'child_process'; import { createHash } from 'crypto'; import { runBashScript } from './helpers/bash-script'; @@ -267,24 +267,6 @@ describe('gstack-team-init', () => { expect(pre.map((e: { matcher: string }) => e.matcher)).toEqual(['Bash', 'Skill|skill']); }); - test('required: the hook denies in both Claude Code and Copilot CLI decision formats (C7)', () => { - run(`${TEAM_INIT} required`, { cwd: tmpDir }); - const home = mkTmpDir(); - try { - const r = spawnSync('bash', [path.join(tmpDir, '.claude', 'hooks', 'check-gstack.sh')], { - encoding: 'utf-8', timeout: 30_000, env: { PATH: process.env.PATH ?? '/usr/bin:/bin', HOME: home }, - }); - expect(r.status).toBe(0); - const out = JSON.parse(r.stdout); - expect(out.permissionDecision).toBe('deny'); - expect(out.permissionDecisionReason).toContain('gstack is required'); - expect(out.hookSpecificOutput).toMatchObject({ hookEventName: 'PreToolUse', permissionDecision: 'deny' }); - expect(r.stderr).toContain('BLOCKED: gstack is not installed'); - } finally { - fs.rmSync(home, { recursive: true, force: true }); - } - }); - test('idempotent: running twice does not duplicate CLAUDE.md section', () => { run(`${TEAM_INIT} optional`, { cwd: tmpDir }); run(`${TEAM_INIT} optional`, { cwd: tmpDir });