Skip to content

Commit 9ac91ea

Browse files
fix: avoid CLAUDE_SKILL_DIR in hook frontmatter
1 parent cab774c commit 9ac91ea

9 files changed

Lines changed: 35 additions & 17 deletions

File tree

careful/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ hooks:
1414
- matcher: "Bash"
1515
hooks:
1616
- type: command
17-
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-careful.sh"
17+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/careful/bin/check-careful.sh" "$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
1818
statusMessage: "Checking for destructive commands..."
1919
---
2020
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->

careful/SKILL.md.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ hooks:
1919
- matcher: "Bash"
2020
hooks:
2121
- type: command
22-
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-careful.sh"
22+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/careful/bin/check-careful.sh" "$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
2323
statusMessage: "Checking for destructive commands..."
2424
sensitive: true
2525
---

freeze/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ hooks:
1515
- matcher: "Edit"
1616
hooks:
1717
- type: command
18-
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-freeze.sh"
18+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
1919
statusMessage: "Checking freeze boundary..."
2020
- matcher: "Write"
2121
hooks:
2222
- type: command
23-
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-freeze.sh"
23+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
2424
statusMessage: "Checking freeze boundary..."
2525
---
2626
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->

freeze/SKILL.md.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ hooks:
2020
- matcher: "Edit"
2121
hooks:
2222
- type: command
23-
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-freeze.sh"
23+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
2424
statusMessage: "Checking freeze boundary..."
2525
- matcher: "Write"
2626
hooks:
2727
- type: command
28-
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-freeze.sh"
28+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
2929
statusMessage: "Checking freeze boundary..."
3030
sensitive: true
3131
---

guard/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@ hooks:
1515
- matcher: "Bash"
1616
hooks:
1717
- type: command
18-
command: "bash ${CLAUDE_SKILL_DIR}/../careful/bin/check-careful.sh"
18+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/careful/bin/check-careful.sh" "$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
1919
statusMessage: "Checking for destructive commands..."
2020
- matcher: "Edit"
2121
hooks:
2222
- type: command
23-
command: "bash ${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"
23+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
2424
statusMessage: "Checking freeze boundary..."
2525
- matcher: "Write"
2626
hooks:
2727
- type: command
28-
command: "bash ${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"
28+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
2929
statusMessage: "Checking freeze boundary..."
3030
---
3131
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->

guard/SKILL.md.tmpl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ hooks:
2020
- matcher: "Bash"
2121
hooks:
2222
- type: command
23-
command: "bash ${CLAUDE_SKILL_DIR}/../careful/bin/check-careful.sh"
23+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/careful/bin/check-careful.sh" "$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
2424
statusMessage: "Checking for destructive commands..."
2525
- matcher: "Edit"
2626
hooks:
2727
- type: command
28-
command: "bash ${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"
28+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
2929
statusMessage: "Checking freeze boundary..."
3030
- matcher: "Write"
3131
hooks:
3232
- type: command
33-
command: "bash ${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"
33+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
3434
statusMessage: "Checking freeze boundary..."
3535
sensitive: true
3636
---

investigate/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ hooks:
2323
- matcher: "Edit"
2424
hooks:
2525
- type: command
26-
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'''
26+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
2727
statusMessage: "Checking debug scope boundary..."
2828
- matcher: "Write"
2929
hooks:
3030
- type: command
31-
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'''
31+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
3232
statusMessage: "Checking debug scope boundary..."
3333
gbrain:
3434
schema: 1

investigate/SKILL.md.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ hooks:
3030
- matcher: "Edit"
3131
hooks:
3232
- type: command
33-
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'''
33+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
3434
statusMessage: "Checking debug scope boundary..."
3535
- matcher: "Write"
3636
hooks:
3737
- type: command
38-
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'''
38+
command: 'bash -c ''R=$(git rev-parse --show-toplevel 2>/dev/null || pwd); for S in "$R/.claude/skills/gstack/freeze/bin/check-freeze.sh" "$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; do [ -x "$S" ] && exec bash "$S"; done; exit 0'''
3939
statusMessage: "Checking debug scope boundary..."
4040
gbrain:
4141
schema: 1

test/gen-skill-docs.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1837,7 +1837,7 @@ describe('Codex generation (--host codex)', () => {
18371837
expect(frontmatter).toContain('YC Office Hours');
18381838
});
18391839

1840-
test('hook skills have safety prose and no hooks: in frontmatter', () => {
1840+
test('Codex hook skills have safety prose and no hooks: in frontmatter', () => {
18411841
const HOOK_SKILLS = ['gstack-careful', 'gstack-freeze', 'gstack-guard'];
18421842
for (const skillName of HOOK_SKILLS) {
18431843
const content = fs.readFileSync(path.join(AGENTS_DIR, skillName, 'SKILL.md'), 'utf-8');
@@ -1850,6 +1850,24 @@ describe('Codex generation (--host codex)', () => {
18501850
}
18511851
});
18521852

1853+
test('Claude hook commands do not depend on CLAUDE_SKILL_DIR', () => {
1854+
// #1871: Claude Code 2.1.162 does not populate CLAUDE_SKILL_DIR for
1855+
// skill-frontmatter PreToolUse hooks. If these commands use that variable,
1856+
// they expand to paths like /../freeze/bin/check-freeze.sh and fail before
1857+
// the safety hook can do its job.
1858+
const HOOK_SKILLS = ['careful', 'freeze', 'guard', 'investigate'];
1859+
for (const skillName of HOOK_SKILLS) {
1860+
const content = fs.readFileSync(path.join(ROOT, skillName, 'SKILL.md'), 'utf-8');
1861+
const fmEnd = content.indexOf('\n---', 4);
1862+
const frontmatter = content.slice(4, fmEnd);
1863+
expect(frontmatter).toContain('hooks:');
1864+
expect(frontmatter).not.toContain('CLAUDE_SKILL_DIR');
1865+
expect(frontmatter).toContain('git rev-parse --show-toplevel');
1866+
expect(frontmatter).toContain('$R/.claude/skills/gstack/');
1867+
expect(frontmatter).toContain('$HOME/.claude/skills/gstack/');
1868+
}
1869+
});
1870+
18531871
test('all Codex SKILL.md files have auto-generated header', () => {
18541872
for (const skill of CODEX_SKILLS) {
18551873
const content = fs.readFileSync(path.join(AGENTS_DIR, skill.codexName, 'SKILL.md'), 'utf-8');

0 commit comments

Comments
 (0)