Files
gstack/careful/SKILL.md
Garry Tanandcapy-ai[bot] c7edf46bcf v1.91.47.0 fix(careful,freeze): safety hooks cover PowerShell and NotebookEdit (#3077)
* fix(careful): check PowerShell tool commands and nested pwsh/cmd (#3067)

/careful and /guard matched only the Bash tool, so on Windows, where
Claude Code's PowerShell tool is the primary shell, the guard never ran.
check-careful.sh also had no PowerShell or cmd patterns.

- careful/guard register a PowerShell matcher beside Bash.
- hook-extract.sh gains gstack_hook_extract_tool: one parser call reads
  tool_name with the field, so dispatch adds no fork.
- check-careful.sh runs one PowerShell/cmd pattern table on PowerShell
  tool commands and on the tail of any Bash command that launches
  pwsh/powershell/cmd: Remove-Item and aliases with -Recurse/-Force
  prefixes, rd/rmdir/del/erase /s, Format-Volume, Clear-Disk,
  Clear-Content, [IO.Directory]/[IO.File]::Delete, and asks with an
  explanation for -EncodedCommand, iex, Start-Process of a shell and
  & $cmd. Command-position anchors keep Bash negative controls allowed.
- /careful documents the coverage as best-effort and points to Claude
  Code permission deny rules.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(freeze): NotebookEdit respects the freeze boundary (#3067)

NotebookEdit was in no freeze/guard/investigate matcher, and
check-freeze.sh read only file_path, so notebook edits ignored /freeze on
every platform. NotebookEdit carries tool_input.notebook_path.

- freeze, guard and investigate register a NotebookEdit matcher beside
  Edit and Write.
- check-freeze.sh reads file_path, then notebook_path, through the shared
  extractor; denials name the tool, the field, the boundary and
  /unfreeze. A helper without the combined reader denies as out of date.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: run hook-scripts on the Windows lane (#3067)

The careful/freeze hooks are Windows coverage, but test/hook-scripts.test.ts
was excluded from windows-free-tests by the raw /tmp/ pattern; its /tmp/
literals are payload strings that are never opened. Admit it by name and
gate the three cases Windows runners cannot host: a symlink (no Developer
Mode), a newline in a directory name, and a POSIX ':' PATH prefix.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore(evals): check-careful.sh is free-only for PR selection

No paid fixture copies or runs careful/bin/check-careful.sh (the
investigate boundary fixture copies hook-extract.sh and check-freeze.sh
only); test/hook-scripts.test.ts covers it. As an unknown dependency it
restored the full paid gate on every /careful hook change.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(careful): pin the #3067 Invoke-Sqlcmd DROP TABLE example on the PowerShell tool

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore(evals): record the check-careful.sh selection change as non-behavioral

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: /careful covers PowerShell (best-effort), /freeze covers NotebookEdit (#3067)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: release v1.91.37.0 (CHANGELOG, TODOS for deferred hook follow-ups)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: re-slot release to v1.91.39.0 (v1.91.37.0 and v1.91.38.0 claimed by #3075, #3076)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: re-slot release to v1.91.41.0 (merge-queue reservation)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(careful): hook payload reader survives Windows text-mode Python and backslash source paths

windows-free-tests (run 37739845723) showed every PowerShell-tool case
falling through: Python's text-mode stdout on Windows wrote tool_name as
"PowerShell\r", so dispatch never matched, and freeze denials carried
stray CRs. gstack_hook_extract_tool now reads and writes bytes.

hook-extract.sh also located gstack-state-root.sh with ${BASH_SOURCE%/*},
which strips nothing when the file is sourced through a C:\... path, so
gstack_hook_state_root vanished (state-root parity case on the same run).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): PR lane selects office-hours-auto-mode by its real test name

The PR paid lane filters audited files with -t built from
CASE_TEST_NAMES[id] ?? id. office-hours-auto-mode's Bun test is named
'AskUserQuestion surfaces when --disallowedTools AskUserQuestion is set',
so the filter matched 0 tests and the shard failed (#3077 eval-slices (6),
run 37741434771). Map the label, and check every audited PR-profile case
is addressable by name. Recorded as non-behavioral for HARNESS_VERSION.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: CHANGELOG notes for the Windows CRLF and PR-lane name fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: re-slot release to v1.91.47.0 (merge-queue assignment)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-08 08:18:26 +00:00

5.0 KiB

name, version, description, triggers, allowed-tools, hooks
name version description triggers allowed-tools hooks
careful 0.1.0 Safety guardrails for destructive commands. (gstack)
be careful
warn before destructive
safety mode
Bash
Read
PreToolUse
matcher hooks
Bash
type command statusMessage
command bash -c "exec bash "$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"" Checking for destructive commands...
matcher hooks
PowerShell
type command statusMessage
command bash -c "exec bash "$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"" Checking for destructive commands...

When to invoke this skill

Warns before rm -rf, DROP TABLE, force-push, git reset --hard, kubectl delete, and similar destructive operations. User can override each warning. Use when touching prod, debugging live systems, or working in a shared environment. Use when asked to "be careful", "safety mode", "prod mode", or "careful mode".

/careful — Destructive Command Guardrails

Safety mode is now active. Every Bash and PowerShell command will be checked for destructive patterns before running. If a destructive command is detected, you'll be warned and can choose to proceed or cancel.

GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
mkdir -p "$GSTACK_STATE_ROOT"/analytics
echo '{"skill":"careful","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}'  >> "$GSTACK_STATE_ROOT"/analytics/skill-usage.jsonl 2>/dev/null || true

What's protected

Pattern Example Risk
rm -rf / rm -r / rm --recursive rm -rf /var/data Recursive delete
DROP TABLE / DROP DATABASE DROP TABLE users; Data loss
TRUNCATE TRUNCATE orders; Data loss
git push --force / -f git push -f origin main History rewrite
git reset --hard git reset --hard HEAD~3 Uncommitted work loss
git checkout . / git restore . git checkout . Uncommitted work loss
kubectl delete kubectl delete pod Production impact
docker rm -f / docker system prune docker system prune -a Container/image loss

PowerShell and cmd (Windows)

The hook also checks the PowerShell tool (Claude Code's main Windows shell) and any pwsh/powershell/cmd launched from Bash. Matching ignores case, covers aliases in command position, accepts parameter prefixes (-r, -fo) and strips cmd ^ escapes. All rows above apply there too.

Pattern Example
Remove-Item/rm/ri/del/erase/rd/rmdir + -Recurse or -Force gci | ri -r -fo
cmd rd /s, rmdir /s, del /s, erase /s cmd /c rd /s /q C:\proj
Format-Volume, Clear-Disk, Clear-Content, [IO.Directory]::Delete, [IO.File]::Delete Clear-Disk -Number 1
-EncodedCommand, iex, Start-Process of a shell, & $cmd (can't be inspected) irm $url | iex

Best-effort on PowerShell. String matching can't see a command built at runtime. For a hard stop, add Claude Code permission deny rules, which parse PowerShell and its aliases: "deny": ["PowerShell(Remove-Item *)"] in .claude/settings.json. The hook runs through bash, so Windows needs Git Bash.

Safe exceptions

These patterns are allowed without warning:

  • rm -rf node_modules / .next / dist / __pycache__ / .cache / build / .turbo / coverage

How it works

The hook reads tool_name and the command from the tool input JSON, checks it against the patterns above, and returns a hookSpecificOutput payload with permissionDecision: "ask" and a warning reason if a match is found (the decision must be nested under hookSpecificOutput — Claude Code ignores a top-level permissionDecision). You can always override a MEDIUM warning and proceed.

HIGH tier (hard deny)

Two catastrophic shapes are denied, not asked: rm -r/-R of exactly /, ~, or $HOME, and force-push to the repo's default branch. SIMPLE commands only (no ;, &&, ||, |, newline) — compound shapes fall through to the MEDIUM ask; --force-with-lease is never HIGH. A best-effort advisory hard-stop, not a policy boundary: the escape hatch is ending the opt-in, session-scoped /careful session.

Project patterns (additive only)

Add warn rules — one POSIX ERE per line, # comments OK — in ~/.gstack/careful-patterns.txt (global) or ~/.gstack/projects/<slug>/careful-patterns.txt (per-project). Consulted after the built-in families, so config can only ADD rules, never suppress a baseline warning. Invalid regex lines are skipped.

To deactivate, end the conversation or start a new one. Hooks are session-scoped.