Set up Git guardrails
SkillAI & modelsUse when a repository needs a guard against force-push, forced reset, clean, branch deletion, working-tree discard, stash drop, reflog expire, or gc prune. Not for remote or irreversible changes.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Set up Git guardrails skill
What this skill tells your AI
The instructions your AI receives, as published by outlinedriven/odin-claude-plugin in plugins/odin-git/skills/git-guardrails/SKILL.md and read by ahel’s review.
Contract
| Field | Bound contract |
|---|---|
| Trigger | A repository needs a tool-time safety net against force-push/reset/clean/branch-delete/discard. |
| Authority | Reversible local: writes only the hook copy under .claude/hooks/ or ~/.claude/hooks/ and one merged hooks.PreToolUse entry in the matching settings file; rollback is deleting the copied script and removing the registered entry. No remote mutation. No VCS, credential, or other file change. |
| Side effect | Copies block-dangerous-git.py and registers it in the chosen settings file PreToolUse; net effect removes destructive capability. |
| Done | All 16 verification payloads exit as expected, the hook is registered, and plain git push still exits 0. |
Inputs
- Scope (required, chosen by the user): project (
.claude/settings.jsonplus.claude/hooks/) or global (~/.claude/settings.jsonplus~/.claude/hooks/). - Hook source:
scripts/block-dangerous-git.pyshipped beside this SKILL.md. - Optional: rule additions or removals, decided before installation.
Procedure
-
Ask the user to choose project or global scope. Mutate nothing before the choice. Done when: the user has chosen project or global scope, with no mutation made.
-
Copy
scripts/block-dangerous-git.pyto the chosen location, project:.claude/hooks/block-dangerous-git.py; global:~/.claude/hooks/block-dangerous-git.py, and runchmod +xon the copy. Leave the skill's source copy untouched. Done when: the hook copy exists at the chosen path, is executable, and the source copy is unchanged. -
Show the default blocked operations, forced pushes and forced refspecs;
reset --hard; forcedclean; forced branch deletion;checkout .andrestore .;stash dropandstash clear;reflog expire;gc --prune=now, and ask whether to add or remove a rule. On approval, edit only the installed copy. When a rule is added or removed, add or remove the corresponding test case in the Step 4 verification matrix so the gate covers the modified policy. Done when: the blocked-operations list is shown, any approved rule change is applied to the installed copy only, and the verification matrix is updated to match. -
Verify before registration. For each payload below, run:
printf '%s\n' '<payload>' | <path-to-hook> printf 'exit=%s\n' "$?"Must exit 2:
{"tool_input":{"command":"git push --force origin main"}}{"tool_input":{"command":"ok && git reset --hard"}}{"tool_input":{"command":"echo ok\ngit reset --hard"}}{"tool_input":{"command":"bash -c \"git reset --hard\""}}{"tool_input":{"command":"bash -lc \"git reset --hard\""}}{"tool_input":{"command":"eval \"git reset\" --hard"}}{"tool_input":{"command":"git clean --force"}}{"tool_input":{"command":"git branch --delete --force"}}{"tool_input":{"command":"git checkout ."}}{"tool_input":{"command":"git stash clear"}}{"tool_input":{"command":"git reflog expire --all"}}{"tool_input":{"command":"git gc --prune=now"}}{"tool_input":{"command":"git push origin +main"}}
Must exit 0:
{"tool_input":{"command":"git push origin main"}}{"tool_input":{"command":"git commit -m \"oops; git reset --hard\""}}{"tool_input":{"command":"git --git-dir=.git status"}}
All sixteen cases must match before registration. A blocked command prints this to stderr and exits 2:
BLOCKED: '<command>' matches dangerous pattern '<pattern>'. The user has prevented you from doing this.Done when: all sixteen payloads exit as expected, the thirteen dangerous commands exit 2 and the three safe ones exit 0, and the BLOCKED stderr message is confirmed.
-
After all sixteen cases pass, merge the entry into the existing
hooks.PreToolUsearray of the chosen settings file. Never overwrite the settings file or discard existing hooks. Done when: the entry is merged into the existinghooks.PreToolUsearray with all prior hooks preserved.Project fragment:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous-git.py" } ] } ] } }Global fragment:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "~/.claude/hooks/block-dangerous-git.py" } ] } ] } }
The hook parses shell quoting, scans every Git invocation, and follows code passed to common shells (bash -c, bash -lc) and eval. It is a guardrail, not a sandbox: a determined caller can still hide Git behind runtime indirection. Do not widen rules or scope beyond what the user approved.
Failure and recovery
Failure classes:
- Payload mismatch: any of the sixteen cases exits other than expected. Do not register; report the payload with expected versus actual exit and classify blocked.
- Script not runnable: missing
python3, failed copy, or failedchmod +x. Stop before verification; make no settings change. - Settings unreadable or invalid JSON: stop without writing, report the parse failure, and never overwrite the file or discard existing hooks.
Partial-result rule: a copied but unregistered script is inert; either complete registration only after all sixteen cases pass or delete the copy.
Rollback: delete the installed hook copy and remove the registered hooks.PreToolUse entry from the chosen settings file.
Blocked result: report BLOCKED: git-guardrails <exact reason> with no settings change made. Never swallow an error; never claim done while any check failed.
Output
An executable hook at the chosen path, then one merged hooks.PreToolUse entry, then the sixteen-line verification transcript, then terminal classification installed (project), installed (global), or blocked: <reason>.
Signals
- GitHub stars
- 35
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
git-guardrails- Source
- github.com/outlinedriven/odin-claude-plugin