Skip to content

fix: extract preamble cleanup into bin scripts to avoid damage-control hook collision#1008

Open
wojtekszkutnik wants to merge 1 commit intogarrytan:mainfrom
wojtekszkutnik:worktree-graceful-riding-shore
Open

fix: extract preamble cleanup into bin scripts to avoid damage-control hook collision#1008
wojtekszkutnik wants to merge 1 commit intogarrytan:mainfrom
wojtekszkutnik:worktree-graceful-riding-shore

Conversation

@wojtekszkutnik
Copy link
Copy Markdown

@wojtekszkutnik wojtekszkutnik commented Apr 15, 2026

Summary

The preamble bash block runs as a single Bash tool call at session start. Users of claude-code-damage-control — a popular PreToolUse hook — get every gstack skill blocked on launch.

The hook's regex rm\s+(-[rfR]+|--recursive|--force)\s.*(/|~|\$HOME) scans the full command text and matches the inline rm -f "$_PF" 2>/dev/null (the -f flag triggers the force group, /dev/null provides the /). The entire preamble is hard-blocked before any of it executes.

We considered replacing -exec rm {} + with find -delete, but the project intentionally moved away from -delete because Safety Net and other non-GNU environments don't support it. Extracting into scripts preserves the POSIX -exec rm while hiding it from hook text scanning.

This PR extracts two cleanup operations from the inline preamble into standalone bin scripts, following the same pattern as gstack-update-check and other existing bin utilities:

  • bin/gstack-session-cleanup — removes stale session tracking files (>120 min). Keeps POSIX -exec rm for compatibility.
  • bin/gstack-pending-finalize — flushes and removes pending telemetry marker files. Preserves the existing zsh-safe find approach.

Hooks scan command text, not script contents. Moving rm and find -exec rm into scripts makes them invisible to the hook while keeping identical behavior.

Note on the telemetry epilogue

The "Telemetry (run last)" bash block (preamble.ts:493) also contains rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" which matches the same regex. We are not changing it here because: (1) the epilogue runs as a separate Bash tool call after the skill completes, so impact is lower, and (2) the .pending-* marker creation is not yet implemented in the codebase — this line is currently a no-op that appears to be infrastructure for a future feature. We don't want to remove or relocate code whose design intent we don't fully understand.

What changed

  • scripts/resolvers/preamble.ts — replaced 12 lines of inline cleanup with two script calls
  • bin/gstack-session-cleanup — new (15 lines)
  • bin/gstack-pending-finalize — new (30 lines)
  • test/gen-skill-docs.test.ts — updated two assertions for new script names
  • 33 SKILL.md files regenerated (--host all), 4 golden fixtures re-synced

Test plan

  • bun test — 654 pass, 1 pre-existing fail (package.json version mismatch)
  • Verified on main: same 654/1 result — zero regressions
  • Damage-control regex returns zero matches on regenerated preamble
  • Both scripts tested: stale files removed, recent files preserved, nonexistent dirs exit cleanly

…l hook collision

The preamble's inline `rm -f` triggers claude-code-damage-control's regex
(`rm\s+(-[rfR]+|--recursive|--force)\s.*(/|~|\$HOME)`), blocking every
gstack skill on launch. Extract session cleanup and pending-telemetry
finalization into standalone bin scripts — hooks scan command text, not
script contents.

Keeps POSIX `-exec rm` (not `-delete`) per prior compatibility decision.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant