This commit is contained in:
maxpetrusenkoagent 2026-07-14 18:36:26 -07:00 committed by GitHub
commit 32965df418
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 70 additions and 17 deletions

View File

@ -14,7 +14,7 @@ hooks:
- matcher: "Bash"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/careful/bin/check-careful.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking for destructive commands..."
---
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->

View File

@ -19,7 +19,7 @@ hooks:
- matcher: "Bash"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/careful/bin/check-careful.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking for destructive commands..."
sensitive: true
---

View File

@ -15,12 +15,12 @@ hooks:
- matcher: "Edit"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking freeze boundary..."
- matcher: "Write"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking freeze boundary..."
---
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->

View File

@ -20,12 +20,12 @@ hooks:
- matcher: "Edit"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking freeze boundary..."
- matcher: "Write"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking freeze boundary..."
sensitive: true
---

View File

@ -15,17 +15,17 @@ hooks:
- matcher: "Bash"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/careful/bin/check-careful.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking for destructive commands..."
- matcher: "Edit"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking freeze boundary..."
- matcher: "Write"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking freeze boundary..."
---
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->

View File

@ -20,17 +20,17 @@ hooks:
- matcher: "Bash"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/careful/bin/check-careful.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking for destructive commands..."
- matcher: "Edit"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking freeze boundary..."
- matcher: "Write"
hooks:
- type: command
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking freeze boundary..."
sensitive: true
---

View File

@ -23,12 +23,12 @@ hooks:
- matcher: "Edit"
hooks:
- type: command
command: 'bash -c ''S="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"; [ -x "$S" ] || S="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"; [ -x "$S" ] && bash "$S" || exit 0'''
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking debug scope boundary..."
- matcher: "Write"
hooks:
- type: command
command: 'bash -c ''S="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"; [ -x "$S" ] || S="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"; [ -x "$S" ] && bash "$S" || exit 0'''
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking debug scope boundary..."
gbrain:
schema: 1

View File

@ -30,12 +30,12 @@ hooks:
- matcher: "Edit"
hooks:
- type: command
command: 'bash -c ''S="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"; [ -x "$S" ] || S="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"; [ -x "$S" ] && bash "$S" || exit 0'''
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking debug scope boundary..."
- matcher: "Write"
hooks:
- type: command
command: 'bash -c ''S="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"; [ -x "$S" ] || S="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"; [ -x "$S" ] && bash "$S" || exit 0'''
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
statusMessage: "Checking debug scope boundary..."
gbrain:
schema: 1

View File

@ -1841,7 +1841,7 @@ describe('Codex generation (--host codex)', () => {
expect(frontmatter).toContain('YC Office Hours');
});
test('hook skills have safety prose and no hooks: in frontmatter', () => {
test('Codex hook skills have safety prose and no hooks: in frontmatter', () => {
const HOOK_SKILLS = ['gstack-careful', 'gstack-freeze', 'gstack-guard'];
for (const skillName of HOOK_SKILLS) {
const content = fs.readFileSync(path.join(AGENTS_DIR, skillName, 'SKILL.md'), 'utf-8');
@ -1854,6 +1854,59 @@ describe('Codex generation (--host codex)', () => {
}
});
test('Claude hook commands do not depend on CLAUDE_SKILL_DIR', () => {
// #1871: Claude Code 2.1.162 does not populate CLAUDE_SKILL_DIR for
// skill-frontmatter PreToolUse hooks. If these commands use that variable,
// they expand to paths like /../freeze/bin/check-freeze.sh and fail before
// the safety hook can do its job.
const HOOK_SKILLS = ['careful', 'freeze', 'guard', 'investigate'];
for (const skillName of HOOK_SKILLS) {
const content = fs.readFileSync(path.join(ROOT, skillName, 'SKILL.md'), 'utf-8');
const fmEnd = content.indexOf('\n---', 4);
const frontmatter = content.slice(4, fmEnd);
expect(frontmatter).toContain('hooks:');
expect(frontmatter).not.toContain('CLAUDE_SKILL_DIR');
expect(frontmatter).not.toContain('CLAUDE_PLUGIN_ROOT');
expect(frontmatter).not.toContain('git rev-parse --show-toplevel');
expect(frontmatter).toContain('while :; do');
expect(frontmatter).toContain('$D/.claude/skills/gstack/');
expect(frontmatter).toContain('$HOME/.claude/skills/gstack/');
}
});
test('Claude hook commands resolve project-local install from nested non-git cwd', () => {
const { execFileSync } = require('child_process');
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-hook-local-'));
const projectRoot = path.join(tmpRoot, 'project');
const nestedCwd = path.join(projectRoot, 'nested', 'plain-dir');
const scriptPath = path.join(projectRoot, '.claude', 'skills', 'gstack', 'careful', 'bin', 'check-careful.sh');
fs.mkdirSync(path.dirname(scriptPath), { recursive: true });
fs.mkdirSync(nestedCwd, { recursive: true });
fs.writeFileSync(scriptPath, '#!/usr/bin/env bash\nprintf local-careful');
fs.chmodSync(scriptPath, 0o755);
try {
const content = fs.readFileSync(path.join(ROOT, 'careful', 'SKILL.md'), 'utf-8');
const fmEnd = content.indexOf('\n---', 4);
const frontmatter = content.slice(4, fmEnd);
const commandLine = frontmatter.split('\n').find(line => line.trim().startsWith('command: '));
expect(commandLine).toBeTruthy();
let command = commandLine!.trim().slice('command: '.length);
expect(command.startsWith("'")).toBe(true);
command = command.slice(1, -1).replaceAll("''", "'");
const output = execFileSync('bash', ['-c', command], {
cwd: nestedCwd,
input: '{}',
encoding: 'utf8',
});
expect(output).toBe('local-careful');
} finally {
fs.rmSync(tmpRoot, { recursive: true, force: true });
}
});
test('all Codex SKILL.md files have auto-generated header', () => {
for (const skill of CODEX_SKILLS) {
const content = fs.readFileSync(path.join(AGENTS_DIR, skill.codexName, 'SKILL.md'), 'utf-8');