Git Guardrails
Status: DISABLED. The hook script exists at
.claude/hooks/guards/git-guardrails.shbut is not wired intosettings.json. It does nothing until re-enabled. See "Re-enable" below.
Upstream: Adapted from mattpocock/skills/git-guardrails-claude-code. Implements harness-level git safety — prompts cannot reliably enforce this, only PreToolUse hooks can.
This skill manages the git-safety hook at .claude/hooks/guards/git-guardrails.sh.
The plugin does not wire a project hook. Resolve the target repository and its local
settings through ../setup/references/task-context.md before any requested change;
never edit the installed plugin copy. A status check is standalone and creates no task;
when a task owns the guard change, save settings read-back and smoke-test receipts there.
Re-enable
Add the following entry to the target repository's Bash-matcher hooks array in its
.claude/settings.json under hooks.PreToolUse:
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guards/git-guardrails.sh"
}
Place it first in the array for fail-fast (so dangerous commands are rejected before logging/validation hooks fire). Remove the DISABLED banner at the top of the script.
What's blocked when wired
Once the hook is wired, it blocks these operations without additional configuration:
git push— any variant. The user pushes manually after reviewing the diff/PR.git branch -D/git branch --delete -f— force-delete branches discards unmerged work.git checkout ./git restore ./git checkout -- .— discarding all working-tree changes.
Already covered by the sibling bash-guard.sh (universal rules, always-active section):
git reset --hardgit push --force/-f(the--forcevariant —git-guardrails.shblocks the rest)git clean -fd/-fxrm -rf /,rm -rf ~,rm -rf .curl | sh/wget | bash
When blocked, Claude sees BLOCKED: … on stderr with exit 2 and stops — the user runs the command manually if intended.
This skill documents and verifies the optional hook; do not change settings.json, enable the
hook, or remove a pattern unless the user explicitly requests that configuration change.
Verify it's wired
# Should be referenced in PreToolUse → Bash hooks:
jq '.hooks.PreToolUse[] | select(.matcher == "Bash") | .hooks[].command' .claude/settings.json
# Smoke test — should print the BLOCKED message and exit 2:
echo '{"tool_input":{"command":"git push origin main"}}' | .claude/hooks/guards/git-guardrails.sh
echo "exit: $?" # expect: 2
Add or remove patterns
Edit .claude/hooks/guards/git-guardrails.sh directly. Each rule is a grep -qE block followed by echo "BLOCKED: …" >&2; exit 2. Rules are independent — add or remove freely.
When adding a rule, also add a smoke-test line above and confirm exit 2.
Removing the guard entirely
- Edit
.claude/settings.jsonand remove thegit-guardrails.shentry fromhooks.PreToolUse[*].hookswhere the matcher isBash. - The script file can stay on disk — it does nothing without the wire-up.
Don't disable on a whim — every blocked command reflects an incident or a near-miss.
Why harness-level, not prompt-level
A CLAUDE.md instruction "do not run git push" is unreliable: the model can ignore it under pressure, and the user can't audit it. A PreToolUse hook is the only enforcement that actually fires every time. This is a load-bearing safety primitive — keep it.