| name | safety-git-reset-guard |
| description | Blocks destructive git commands (reset --hard, push --force, clean -f, checkout <path>, branch -D, stash drop/clear, worktree remove --force) in Claude Code while letting safer alternatives (--force-with-lease, --soft/--mixed, restore, branch -d) through. PreToolUse hook on Bash. Frontmatter block fires only when this skill is active in context; run `scripts/install.sh` after `npx skills add` for always-on protection. |
| hooks | {"PreToolUse":[{"matcher":"Bash","type":"command","command":"~/.claude/skills/safety-git-reset-guard/scripts/run.sh"}]} |
safety-git-reset-guard
A PreToolUse hook that intercepts every Bash tool call, scans the command
string for destructive git invocations, and blocks the call with a message
pointing at safer alternatives. It blocks by printing a
permissionDecision: "deny" JSON object on stdout and exiting 0 — the
PreToolUse contract that both Claude Code and Codex honor (see
How it works and Codex CLI). Quoted strings are
stripped first so echo 'git reset --hard' and
git commit -m "fix reset --hard bug" are unaffected.
This is a defense layer, not a guarantee. A motivated adversary or a
sufficiently creative invocation can bypass any regex check. Run it alongside
sandboxing, code review, and backups — not in place of them.
Blocked patterns
| Class | Examples |
|---|
git reset --hard | any target — HEAD, commit SHAs, branches, refs |
git push --force / -f | unconditional force push (--force-with-lease is allowed) |
git clean -f* / --force | -f, -fd, -fdx, -xf, --force |
git checkout <path> | git checkout ., git checkout -- file (worktree-discard form) |
git branch -D / --delete --force | force-deleting unmerged branches |
git stash drop / clear | dropping individual stashes or clearing the list |
git worktree remove --force / -f | force-removing dirty worktrees |
Bypass coverage
- Path variants:
/usr/bin/git, ./git, \git
- Wrappers:
sudo git, command git, env git, xargs git
- Subshells:
sh -c '...', bash -c '...', zsh -c '...', dash -c '...'
- Chained:
cmd && git reset --hard, cmd; git push --force, etc.
Allowed (explicitly)
git reset (no flag), git reset --soft, git reset --mixed
git push --force-with-lease, git push --force-with-lease=ref:expected
git checkout main, git checkout -b feature
git clean -n / --dry-run
git stash, git stash push, git stash pop, git stash apply
git branch -d merged-branch (git itself refuses if unmerged)
- Quoted strings:
echo 'git reset --hard', git commit -m "fix reset --hard bug"
Install
npx skills add zcaceres/skills -s safety-git-reset-guard
~/.claude/skills/safety-git-reset-guard/scripts/install.sh
The second step wires this skill's PreToolUse:Bash hook into
~/.claude/settings.json so it fires on every Bash call, not just when
this skill is active in context. The script is idempotent, backs up the
target file with a timestamp, and is a no-op if the hook is already
wired. Flags: --project, --target PATH. Requires jq.
Frontmatter hooks: blocks fire only while the skill is loaded into
context, so they're not real always-on protection — install.sh closes
that gap. See
safety-rm-rf-guard's Install section
for the full explanation.
You can stack this alongside safety-rm-rf-guard — both
hooks run on every Bash call and either can block.
Manual wiring (alternative)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "<path>/safety-git-reset-guard/scripts/run.sh" }
]
}
]
}
}
On Windows, point at scripts\\run.cmd instead.
Codex CLI
The same binary works on Codex CLI. Codex's hook engine delivers the same
stdin JSON payload (tool_input.command) and honors the same PreToolUse
permissionDecision: "deny" stdout contract this guard emits — so no rebuild
or variant binary is needed. Codex does not read the hooks: frontmatter,
so the install is manual.
Add to ~/.codex/config.toml (note the top-level [[PreToolUse]] table —
not [[hooks.PreToolUse]]):
[[PreToolUse]]
matcher = "Bash"
[[PreToolUse.hooks]]
type = "command"
command = "/abs/path/to/safety-git-reset-guard/scripts/run.sh"
timeout = 30
After editing, run /hooks inside Codex to review and trust the new hook
— Codex registers user-defined hooks as untrusted until you approve them.
Known limitation. Codex's PreToolUse doesn't intercept every shell
invocation yet — the newer unified_exec streaming path has incomplete
coverage. The guard catches the common Bash-tool calls but is best-effort
on Codex, not airtight. Pair it with sandboxing and backups as you would on
Claude Code.
You can stack this alongside
safety-rm-rf-guard on Codex too —
add a second [[PreToolUse.hooks]] entry under the same [[PreToolUse]]
matcher pointing at its run.sh.
How it works
- Claude Code invokes the hook before every
Bash tool call.
scripts/run.sh picks the right bundled binary for the host OS/arch.
- The binary reads the JSON payload from stdin, extracts
tool_input.command.
- Quoted substrings are stripped (keeping quoted flag-looking tokens like
'--hard' since bash unquotes them at exec).
- Each rule's regex is anchored to a "git invocation": start-of-command, after
a shell operator (
&&, ||, ;, |, $(, backtick, newline), or behind
a known wrapper (sudo, command, env, xargs).
- For
bash -c '...' style subshells, rules re-run against the original
(unstripped) command so dangerous payloads inside quotes still trip.
- On match: the binary prints a
PreToolUse JSON object on stdout —
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "BLOCKED: …"}}
— and exits 0, which both Claude Code and Codex read as "block this call
and show the reason." Otherwise it prints nothing and exits 0 to allow.