Skip to content

Commit 08a6915

Browse files
v1.35.0.1 fix: /freeze and /careful enforcement chain (#1459)
Three independent bugs all required for hooks to fire and enforce: Bug 1 — bin/ not symlinked (hook script unreachable) Bug 2 — hooks not registered in settings.json (never fires) Bug 3 — wrong hookSpecificOutput format (fires, deny ignored) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0fa15d1 commit 08a6915

5 files changed

Lines changed: 39 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
# Changelog
22

3+
## [1.45.4.0] - 2026-05-28
4+
5+
**`/freeze` and `/careful` now actually block. Three independent bugs — no bin/ symlink, no hook registration, wrong response format — were all required for the hooks to fire and enforce.**
6+
7+
The `/freeze` skill blocks Edit and Write outside a declared directory. `/careful` warns before destructive Bash commands. Both worked during skill invocation (state file written, UI feedback shown) but provided zero actual enforcement. An Edit on a blocked file would succeed silently. Telemetry logged a `boundary_deny` event, which made it look like the hook fired — it did fire, the deny was just ignored.
8+
9+
### The three numbers that matter
10+
11+
Root cause: `check-freeze.sh` and `check-careful.sh` ran fine when called directly. The problem was in how they were wired, not in the pattern-matching logic.
12+
13+
| Bug | Symptom | Root cause |
14+
|-----|---------|-----------|
15+
| Bug 1 | Hook script not found | `link_claude_skill_dirs` symlinked `SKILL.md` but not `bin/` — `${CLAUDE_SKILL_DIR}/bin/check-freeze.sh` resolved to a non-existent path |
16+
| Bug 2 | Hook never fired | SKILL.md frontmatter `hooks:` is documentation — Claude Code only fires hooks registered in `~/.claude/settings.json`. Setup never wrote there |
17+
| Bug 3 | Hook fired, deny ignored | Scripts returned `{"permissionDecision":"deny","message":"..."}` — a flat object Claude Code doesn't recognise. Correct format is `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}}` |
18+
19+
With all three fixed, `/freeze` enforces the boundary on Edit, Write, and MultiEdit. `/careful` warns on destructive Bash commands when active. Both work in every new session after re-running `./setup`.
20+
21+
### What this means for users who ran `/freeze` or `/careful` before
22+
23+
Re-run `./setup` once. Setup now symlinks each skill's `bin/` directory and writes the PreToolUse hook entries to `~/.claude/settings.json`. After that, `/freeze` actually blocks edits outside the declared directory. `/careful` is a no-op unless you invoke it (gated by `~/.gstack/careful-active.txt`), so it won't interfere with sessions where you didn't ask for safety mode.
24+
25+
### Itemized changes
26+
27+
#### Fixed
28+
29+
- **Bug 1** — `setup` and `gstack-relink` now symlink each skill's `bin/` directory alongside `SKILL.md` so hook scripts in the frontmatter are reachable at `${CLAUDE_SKILL_DIR}/bin/`
30+
- **Bug 2** — `setup` registers PreToolUse entries for freeze (Edit|Write|MultiEdit) and careful (Bash) in `~/.claude/settings.json` at install time via the extended `gstack-settings-hook`; `gstack-settings-hook` gains `add-pretooluse` and `remove-pretooluse` actions
31+
- **Bug 3** — `check-freeze.sh` now outputs `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}}` as required; `check-careful.sh` does the same for `permissionDecision:"ask"`
32+
- **Bonus** — `check-freeze.sh` resolves its state root via `gstack-paths` to honour `GSTACK_HOME` in addition to `CLAUDE_PLUGIN_DATA`; `check-careful.sh` is a no-op unless `~/.gstack/careful-active.txt` exists (created when `/careful` is invoked)
33+
34+
#### For contributors
35+
36+
- `test/hook-scripts.test.ts`: all careful tests use `withCarefulActive()` and pass `CLAUDE_PLUGIN_DATA` pointing to a temp dir; freeze tests assert `hookSpecificOutput` shape; two new tests verify the envelope structure and confirm allow responses remain `{}`
37+
338
## [1.45.0.0] - 2026-05-25
439

540
## **Design boards now live 24 hours, not 10 minutes. One daemon hosts every board, one tab survives the whole day.**

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.45.0.0
1+
1.45.4.0

freeze/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ The hook reads `file_path` from the Edit/Write tool input JSON, then checks
7676
whether the path starts with the freeze directory. If not, it returns
7777
a `hookSpecificOutput` deny decision to block the operation.
7878

79-
The hook is registered globally in `~/.claude/settings.json` by the gstack
79+
The hook is registered globally in Claude Code's settings by the gstack
8080
installer. It is a no-op when no freeze state file exists, so it does not
8181
interfere in sessions where `/freeze` has not been invoked. The freeze
8282
boundary persists via the state file; `/unfreeze` clears it.

freeze/SKILL.md.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ The hook reads `file_path` from the Edit/Write tool input JSON, then checks
7575
whether the path starts with the freeze directory. If not, it returns
7676
a `hookSpecificOutput` deny decision to block the operation.
7777

78-
The hook is registered globally in `~/.claude/settings.json` by the gstack
78+
The hook is registered globally in Claude Code's settings by the gstack
7979
installer. It is a no-op when no freeze state file exists, so it does not
8080
interfere in sessions where `/freeze` has not been invoked. The freeze
8181
boundary persists via the state file; `/unfreeze` clears it.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gstack",
3-
"version": "1.45.0.0",
3+
"version": "1.45.4.0",
44
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
55
"license": "MIT",
66
"type": "module",

0 commit comments

Comments
 (0)