mirror of https://github.com/garrytan/gstack.git
fix(investigate): anchor the scope-lock freeze hook on $HOME, not CLAUDE_SKILL_DIR
The investigate skill's PreToolUse hooks and Scope Lock probe resolved
check-freeze.sh via ${CLAUDE_SKILL_DIR}, which does not exist when
frontmatter hooks run — the || exit 0 tail then failed open, so the debug
scope boundary silently never engaged (#1871 follow-up). Anchor all four
sites on $HOME/.claude/skills/gstack/ like careful/freeze, and add a static
test asserting no frontmatter command: line in the guard-family skills ever
references CLAUDE_SKILL_DIR again.
Fixes #2469; closes the last live half of #1459 together with the
freeze/careful hookSpecificOutput fix. The broader portable-install-root
rewrite stays #1882 (its own focused PR per the TODOS.md decision).
Reported with a fix by @maxpetrusenkoagent (PR #1873; absorbed narrowly —
the cwd-walk rewrite belongs to #1882).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e684948efe
commit
3fa7739689
|
|
@ -23,12 +23,12 @@ hooks:
|
||||||
- matcher: "Edit"
|
- matcher: "Edit"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- 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 ''S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking debug scope boundary..."
|
statusMessage: "Checking debug scope boundary..."
|
||||||
- matcher: "Write"
|
- matcher: "Write"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- 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 ''S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking debug scope boundary..."
|
statusMessage: "Checking debug scope boundary..."
|
||||||
gbrain:
|
gbrain:
|
||||||
schema: 1
|
schema: 1
|
||||||
|
|
@ -910,8 +910,10 @@ If any learnings come back, name which one applies to your investigation in one
|
||||||
After forming your root cause hypothesis, lock edits to the affected module to prevent scope creep.
|
After forming your root cause hypothesis, lock edits to the affected module to prevent scope creep.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
_FREEZE_SCRIPT="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"
|
# $HOME-anchored like the careful/freeze frontmatter hooks (#1871): frontmatter
|
||||||
[ -x "$_FREEZE_SCRIPT" ] || _FREEZE_SCRIPT="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"
|
# hooks and early skill bash run before any runtime var like CLAUDE_SKILL_DIR
|
||||||
|
# exists, so a ${CLAUDE_SKILL_DIR}-relative path silently never resolves (#2469).
|
||||||
|
_FREEZE_SCRIPT="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
||||||
[ -x "$_FREEZE_SCRIPT" ] && echo "FREEZE_AVAILABLE" || echo "FREEZE_UNAVAILABLE"
|
[ -x "$_FREEZE_SCRIPT" ] && echo "FREEZE_AVAILABLE" || echo "FREEZE_UNAVAILABLE"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,12 @@ hooks:
|
||||||
- matcher: "Edit"
|
- matcher: "Edit"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- 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 ''S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking debug scope boundary..."
|
statusMessage: "Checking debug scope boundary..."
|
||||||
- matcher: "Write"
|
- matcher: "Write"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- 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 ''S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking debug scope boundary..."
|
statusMessage: "Checking debug scope boundary..."
|
||||||
gbrain:
|
gbrain:
|
||||||
schema: 1
|
schema: 1
|
||||||
|
|
@ -118,8 +118,10 @@ If any learnings come back, name which one applies to your investigation in one
|
||||||
After forming your root cause hypothesis, lock edits to the affected module to prevent scope creep.
|
After forming your root cause hypothesis, lock edits to the affected module to prevent scope creep.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
_FREEZE_SCRIPT="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"
|
# $HOME-anchored like the careful/freeze frontmatter hooks (#1871): frontmatter
|
||||||
[ -x "$_FREEZE_SCRIPT" ] || _FREEZE_SCRIPT="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"
|
# hooks and early skill bash run before any runtime var like CLAUDE_SKILL_DIR
|
||||||
|
# exists, so a ${CLAUDE_SKILL_DIR}-relative path silently never resolves (#2469).
|
||||||
|
_FREEZE_SCRIPT="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
||||||
[ -x "$_FREEZE_SCRIPT" ] && echo "FREEZE_AVAILABLE" || echo "FREEZE_UNAVAILABLE"
|
[ -x "$_FREEZE_SCRIPT" ] && echo "FREEZE_AVAILABLE" || echo "FREEZE_UNAVAILABLE"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,27 @@ function withFreezeDir(freezePath: string, fn: (stateDir: string) => void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Frontmatter hook wiring (#2469 / #1871)
|
||||||
|
// ============================================================
|
||||||
|
// Frontmatter hooks run before any runtime variable exists, so a
|
||||||
|
// ${CLAUDE_SKILL_DIR}-relative command silently never resolves and the guard
|
||||||
|
// never fires. Every command: line must anchor on $HOME like careful/freeze.
|
||||||
|
describe('frontmatter hook command paths', () => {
|
||||||
|
test.each(['investigate/SKILL.md', 'careful/SKILL.md', 'freeze/SKILL.md', 'guard/SKILL.md'])(
|
||||||
|
'%s hook commands are $HOME-anchored, never CLAUDE_SKILL_DIR',
|
||||||
|
(rel) => {
|
||||||
|
const content = fs.readFileSync(path.join(ROOT, rel), 'utf-8');
|
||||||
|
const commandLines = content.split('\n').filter((l) => l.trim().startsWith('command:'));
|
||||||
|
expect(commandLines.length).toBeGreaterThan(0);
|
||||||
|
for (const line of commandLines) {
|
||||||
|
expect(line).not.toContain('CLAUDE_SKILL_DIR');
|
||||||
|
expect(line).toContain('$HOME/.claude/skills/gstack/');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// check-careful.sh tests
|
// check-careful.sh tests
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,29 @@ import * as path from 'path';
|
||||||
const ROOT = path.resolve(import.meta.dir, '..');
|
const ROOT = path.resolve(import.meta.dir, '..');
|
||||||
const FILES = ['investigate/SKILL.md.tmpl', 'investigate/SKILL.md'];
|
const FILES = ['investigate/SKILL.md.tmpl', 'investigate/SKILL.md'];
|
||||||
|
|
||||||
|
// #2469: frontmatter hooks (and early skill bash) run before any runtime
|
||||||
|
// variable exists, so a ${CLAUDE_SKILL_DIR}-relative path silently never
|
||||||
|
// resolved and the scope-lock guard failed open via `|| exit 0`. Both the
|
||||||
|
// hook commands and the Scope Lock probe must anchor on $HOME like the
|
||||||
|
// careful/freeze skills (#1871). The old standalone `gstack-freeze` sibling
|
||||||
|
// fallback was part of the never-resolving path — prefix installs keep the
|
||||||
|
// payload at ~/.claude/skills/gstack/, so the $HOME anchor covers them.
|
||||||
describe('investigate freeze path resolution', () => {
|
describe('investigate freeze path resolution', () => {
|
||||||
for (const rel of FILES) {
|
for (const rel of FILES) {
|
||||||
const content = fs.readFileSync(path.join(ROOT, rel), 'utf-8');
|
const content = fs.readFileSync(path.join(ROOT, rel), 'utf-8');
|
||||||
|
|
||||||
test(`${rel} hook falls back to standalone gstack-freeze install`, () => {
|
test(`${rel} hook resolves check-freeze via the $HOME anchor`, () => {
|
||||||
expect(content).toContain('${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh');
|
expect(content).toContain('S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"');
|
||||||
expect(content).toContain('${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh');
|
expect(content).toContain('[ -x "$S" ] && exec bash "$S"; exit 0');
|
||||||
expect(content).toContain('[ -x "$S" ] && bash "$S" || exit 0');
|
const commandLines = content.split('\n').filter((l) => l.trim().startsWith('command:'));
|
||||||
expect(content).toContain("command: 'bash -c ''");
|
expect(commandLines.length).toBeGreaterThan(0);
|
||||||
|
for (const line of commandLines) {
|
||||||
|
expect(line).not.toContain('CLAUDE_SKILL_DIR');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`${rel} scope lock availability check supports standalone install`, () => {
|
test(`${rel} scope lock availability probe uses the $HOME anchor`, () => {
|
||||||
expect(content).toContain('_FREEZE_SCRIPT="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"');
|
expect(content).toContain('_FREEZE_SCRIPT="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"');
|
||||||
expect(content).toContain('[ -x "$_FREEZE_SCRIPT" ] || _FREEZE_SCRIPT="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"');
|
|
||||||
expect(content).toContain('[ -x "$_FREEZE_SCRIPT" ] && echo "FREEZE_AVAILABLE" || echo "FREEZE_UNAVAILABLE"');
|
expect(content).toContain('[ -x "$_FREEZE_SCRIPT" ] && echo "FREEZE_AVAILABLE" || echo "FREEZE_UNAVAILABLE"');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue