agentleFS
EtanHeyEtanHey/golems/skills/golem-powers/git-guardian/SKILL.md

git-guardian

Skill0 starsChanged 15 days agoApache-2.0View on GitHub

Safety gate for destructive git/main commits. Triggers: force-push, reset, branch delete, clean, main commit.

  • Reads credentials
  • Deletes or force-pushes
  • Commits and pushes

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

21 rules. About 0 are common, 16 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈16

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    If on main or master: STOP. Do not proceed with force-push, reset --hard, or unprotected commits.

    line 35
  2. Distinctive:

    If there are uncommitted changes: name them explicitly before asking for confirmation.

    line 46
  3. Distinctive:

    Never silently proceed past uncommitted changes.

    line 47
  4. Distinctive:

    Check current branch — block if main/master unconditionally.

    line 69
  5. Very specific:

    Run git log origin/<branch>..HEAD --oneline to show what will be overwritten.

    line 70
  6. Distinctive:

    Treat remote branch or tag deletion like force push: inspect the impact and require explicit user confirmation.

    line 75
  7. Very specific:

    Run git log HEAD~<N>..HEAD --oneline to list commits being dropped.

    line 81
  8. Distinctive:

    Run git status --short to show uncommitted work that will be lost.

    line 82
  9. Distinctive:

    If uncommitted changes exist: explicitly list each file.

    line 83
  10. Distinctive:

    Run git branch --merged to check if the branch is merged.

    line 87
  11. Distinctive:

    If not merged: list the N commits that exist only on this branch.

    line 88
  12. Distinctive:

    Ask for explicit confirmation.

    line 90
  13. Distinctive:

    Run git clean -n (dry run) to list files that would be deleted.

    line 98
  14. Distinctive:

    Ask for explicit confirmation.

    line 100
  15. Distinctive:

    Do NOT commit or push. Redirect the user.

    line 105
  16. Very specific:

    Never run any git command with --no-verify.

    line 108
  17. Distinctive:

    Instead: investigate why the hook is failing and fix the root cause.

    line 109
  18. Distinctive:

    If user insists, surface this as a hard stop: "The hook failure is protecting you.

    line 110
  19. Very specific:

    NEVER: use --no-verify to bypass hooks.

    line 115
  20. Distinctive:

    NEVER: delete an unmerged branch without explicit confirmation.

    line 117
  21. Distinctive:

    If user overrides a warning ("I know, just do it"), proceed — but log what was overridden.

    line 118

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Git Guardian

Prevents git footguns by checking blast radius, uncommitted state, and branch safety before any destructive operation.

Trigger Operations

Invoke this skill when about to run ANY of these:

OperationRisk
git push --force / git push -fOverwrites upstream history
git reset --hardDestroys local changes permanently
git branch -D / git branch -dMay lose unmerged commits
git checkout . / git restore .Destroys uncommitted local changes
git clean -f / git clean -fdPermanently deletes untracked files
git rebase on a shared branchRewrites shared history
Commit or push while on main/masterPollutes main branch history
--no-verify on any commandBypasses safety hooks

Protocol

For every destructive operation, run these checks in order:

1. Branch Safety Check

git branch --show-current
  • If on main or master: STOP. Do not proceed with force-push, reset --hard, or unprotected commits. Redirect to a feature branch.
  • Exception: git pull --rebase and git merge are safe on main.

2. Uncommitted Changes Check

git status --short
git stash list
  • List any modified, staged, or untracked files that would be destroyed.
  • If there are uncommitted changes: name them explicitly before asking for confirmation.
  • Never silently proceed past uncommitted changes.

3. Impact Summary

Before executing, show:

⚠️ Git Guardian — [OPERATION] on [BRANCH]

Commits to be lost:    [N commits, with short hashes + messages]
Files to be lost:      [list of modified/untracked files]
Branch status:         [merged | UNMERGED — N commits only here]
Upstream:              [N commits ahead/behind origin/branch]

Proceed? [y/N]

Only proceed after explicit user confirmation — or if user explicitly says "yes, proceed", "force it", "I know".

4. Specific Rules by Operation

Force Push (--force / -f)

  1. Check current branch — block if main/master unconditionally.
  2. Run git log origin/<branch>..HEAD --oneline to show what will be overwritten.
  3. Show confirmation summary. Proceed only after user confirms.
  4. Never suggest --force-with-lease as a workaround to silently bypass this check.

Remote reference deletion

Treat remote branch or tag deletion like force push: inspect the impact and require explicit user confirmation. The shared policy blocks deletion options, empty-source reference mappings, and bulk pruning or mirroring capabilities. Preview mode retains the same gate. Repository addresses and transport option values are not references.

Reset Hard (reset --hard)

  1. Run git log HEAD~<N>..HEAD --oneline to list commits being dropped.
  2. Run git status --short to show uncommitted work that will be lost.
  3. If uncommitted changes exist: explicitly list each file. Ask for confirmation.
  4. Proceed only after explicit confirmation.

Branch Delete (-D / -d)

  1. Run git branch --merged to check if the branch is merged.
  2. If not merged: list the N commits that exist only on this branch.
  3. Warn: "These commits are not in any other branch. Deleting will make them unrecoverable without git reflog."
  4. Ask for explicit confirmation.

Checkout/Restore (. or path)

  1. List the files that will be reverted.
  2. Show which files have staged changes that would also be dropped.
  3. Ask for confirmation.

Clean (-f)

  1. Run git clean -n (dry run) to list files that would be deleted.
  2. Flag any .env* or secret-looking files prominently.
  3. Ask for explicit confirmation. Never auto-clean.

Commit/Push on main/master

  1. Detect current branch = main or master.
  2. Suggest: "Create a feature branch first: git checkout -b feat/<name>"
  3. Do NOT commit or push. Redirect the user.

--no-verify

  1. Never run any git command with --no-verify.
  2. Instead: investigate why the hook is failing and fix the root cause.
  3. If user insists, surface this as a hard stop: "The hook failure is protecting you. What does the error say?"

Non-Negotiables

  • NEVER force-push to main/master under any circumstances.
  • NEVER use --no-verify to bypass hooks.
  • NEVER silently proceed past uncommitted changes.
  • NEVER delete an unmerged branch without explicit confirmation.
  • If user overrides a warning ("I know, just do it"), proceed — but log what was overridden.

Mechanical checks (gen-18 Track 6 D6 — build the gate, not the prose)

The mechanical rules are pure, importable, replayably-tested functions in git_safety.py (RED→GREEN fixtures in tests/test_git_safety.py, run python3 -m pytest tests/). The active ~/.claude/hooks/pre_tool_use.py imports these instead of re-deriving the rules as prose:

FunctionCatches
is_destructive_restore(command, owned_paths)git checkout -- … / git restore … / git checkout . that would discard UNOWNED in-session changes — returns a git stash suggestion. Distinguishes a working-tree discard from a bare branch switch.
pr_body_is_empty(body)gh pr create with a blank / template-only body (post-create non-empty assert).
is_unauthorized_no_verify(command, authorized)--no-verify on commit/push without authorization (git push -n = --dry-run is correctly NOT flagged).
is_dangerous_rm(command, cwd, env)Resolves shell assignments and cwd before judging recursive-delete breadth; a repo-contained literal prefix suppresses false blocks when a suffix remains dynamic.
shell_text_without_heredoc_bodies(command)Removes file-write heredoc prose from destructive scans while retaining executable substitutions in unquoted heredocs.
dangerous_shell_reason(command, cwd, env)Combined hook-facing F8 verdict for rm breadth and destructive command patterns.

#501 denies recursive rm (with or without force) of ~/Gits, ancestors of the active checkout, direct child directories of $HOME, and directory entries at the first level of ~/.claude, ~/.codex, ~/.cmux, ~/.config, ~/.ssh and ~/Library. Existing paths use filesystem identity (including case and volume aliases); unavailable identity falls back to case-folded physical spelling. Known regular files remain eligible for cleanup and atomic moves. Inside ~/Gits, targets outside a checkout are probed for .git at depth at most 3 with a 5,000-entry cap; cap or filesystem errors fail closed. The initial checkout stays protected after cd; ~, ~+ and ~- use tracked HOME/cwd/oldpwd, and other tilde prefixes fail closed.

An exact recursive rm operand at repo depth 1 can pass when its existing directory is not a symlink, resolves within the same checkout, is Git-ignored, has no tracked index entries beneath it, and contains no worktree listed by git worktree list --porcelain -z. Its resolved basename must exactly match this case-sensitive build-output allowlist: .build, .build.noindex, build, dist, node_modules, .next, .turbo, .parcel-cache, DerivedData, target, __pycache__, .pytest_cache, .mypy_cache, .ruff_cache, .gradle, coverage. Other ignored names, including docs.local, .worktrees, .env.d, and scratch, retain the breadth denial. Git inspection errors retain that denial as well. Protected roots and metadata, globs, loop-driven cleanup, and other deletion tools receive no new exception.

The same target policy covers find -delete roots and local rsync --delete*/--del destinations. Find follows its selected -H/-L/-follow policy (last -P overrides earlier flags); local rsync destinations are evaluated physically. Both also fail closed for targets affected by earlier path creation. Rsync option values are separated from operands; unknown trailing long options fail closed when the destination becomes ambiguous. Find collects BSD/bfs root operands throughout the expression, separating primary values and nested command operands; unknown primaries fail closed. Selective in-repo filters can permit cleanup while retaining home/config/container and active-ancestor protections. Unfiltered, broad, negated or ambiguous branches retain breadth checks. Regex dialects and wildcard-only filename classes do not qualify for a breadth exemption. Positive age filters below a top-level directory retain the repo-root boundary. Filename waivers preserve repository metadata: fixed names and object-name shapes stay protected, and a read-only traversal checks actual metadata names below selected roots and followed directory aliases. Outer depth limits come from the root parser. Directory/metadata discovery refuses above one million entries, two seconds or 64 levels, on cycles, or on filesystem errors. Regular Finder litter is exempt; directories and aliases with the same spelling remain protected. A grouped prune arm retains the positive deletion filter, but deletion implies depth-first traversal, so descendant metadata is still inspected. Ordinary nonmetadata regular files do not consume that discovery budget. Discovery reuses directory-entry information; dependency and worktree names never waive nested metadata. Limit cleanup to literal subtrees and explicit depth limits when an unbounded repository walk exceeds the fixed policy budget. An over-budget refusal explains that the cleanup target is too large to verify and suggests -maxdepth N or a narrower root. It remains a normal policy deny. Moving protected roots is denied; deleting a path affected by an earlier ln, mv or recursive cp fails closed. Removing an existing symlink itself, safe deep cleanup and sanctioned disposable fixtures remain allowed. Earlier mkdir retains protected directory roles while permitting known deep cleanup.

The opt-in scripts/cleanup_corpus_gate.py compares decision functions on exactly 200 frozen local commands. Capture requires --capture-projects; CI uses only synthetic fixtures. It never executes commands and retains hashes plus private source references. Each new denial requires a true-positive reason and evidence; unclassified denials, missing frozen rows or a truncated sample fail the gate. An exact in-repo find . -name __pycache__ -type d -prune -exec rm -r {} + cache cleanup also remains allowed (including -R, -rf, -fr).

This is a bounded shell model, not a shell interpreter. Nonstandard containers outside ~/Gits and the active checkout, remote rsync destinations, trash, interpreter deletion one-liners and filesystem changes between evaluation and execution remain outside its boundary. The lead tracks interpreter deletion as a separate issue; passing this policy is not permission to delete.

Heredoc data masking also recognizes known stdin-preserving wrappers and one simple SSH remote interpreter or literal cat/tee file write. Shell readers, compound/uncertain remote commands and piped output retain body scanning; unquoted heredocs retain executable substitutions. Parse failures deny with a parse-failure explanation, without claiming a destructive git operation.

Wrapper evaluation is capped at 64 nested commands. Deeper input and any RecursionError fail closed with a value-free reason. The hook also converts unexpected policy-evaluation exceptions to a value-free block. The installed git-guardian gate is pre_tool_use.py (source hooks/pre_tool_use.py), which imports this skill's git_safety.py; it is distinct from the project block-dangerous-commands.py and the human-confirm gate. Both host manifests run it through golems-fail-open.py --fail-closed (#488). A missing, unreadable, syntax-broken or crashed hook denies with the plain refusal: "Blocked: a golems safety check couldn't run. Ask the lead to reinstall the hooks." Recovery uses ! bash ~/Gits/golems/scripts/hooks/install-hooks.sh --host <host> --update --apply. Blocked Bash cannot perform agent recovery. A human uses the prompt or an outside terminal (omit ! there). The installer clears the missing pin's locked registration, and --update moves a broken pin to a healthy commit. Local damage inside hooks-live refuses (tamper evidence) until a human inspects it and re-runs with --restore-live. The prompt's ! bypass remains the #411 assumption, not live-verified here. Legitimate allow/deny results pass through. Other gates keep the default fail-open launcher mode; tmp-block and human-confirm are the other policy gates. The launcher is an installed copy so it survives a dangling hooks-live tree. The registered command wraps it in a /bin/sh guard, so a missing pinned interpreter or a launcher that cannot start also denies; a skipped harness registration remains outside its own enforcement boundary. Lead installs through hooks-live after review/merge. A hang (import-time included) is cut by the launcher's --budget watchdog, registered 1 s under the manifest timeout (5 s timeout, 4 s budget): the hook's process group is killed and the call denied before the harness timeout, which would allow. Known residuals: registration/launcher edits can disable enforcement, a hook subprocess that leaves the process group outlives the kill, and only Python-level stdout/stderr is captured.

The "discard only what THIS session owns" rule is the key nuance: discarding your own in-session edits is fine; discarding another agent's or the user's uncommitted work is the footgun — prefer git stash so it is recoverable.

Integration

  • /pr-loop step 5 — git-guardian's branch check is a prerequisite to commit; pr-loop handles CodeRabbit review.
  • /pr-loop — calls git-guardian before any force-push during rebase/fixup cycle.
  • Native git worktree — worktrees always operate on non-main branches; git-guardian still applies for reset/clean inside worktrees.

Bare repositories are recognized by their HEAD file and object/reference directories. Their roots, containing repository folders, and selected metadata receive the same protection as checkout metadata. Multi-root selective cleanup shares one two-second traversal deadline; exhaustion keeps the actionable narrowing guidance. The three-second hook deadline remains unchanged.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.