Skip to content
Agentic Control Plane
Incident series · Part 4 of 11
Stop Your Agent From… →

Stop your AI agent from rewriting your git history — in three steps

David Crowe David Crowe · · 7 min read
governance defense-in-depth git-safety version-control
Share X HN LinkedIn

A widely-shared Hacker News report initially blamed Claude Code for running git reset --hard origin/main every ten minutes inside an automation loop — silently discarding uncommitted work each cycle. The reporter later traced the resets to their own GitPython-based polling tool, not the agent — though the uncommitted work was still gone, and there’s no --undo for git reset --hard. The failure mode itself is confirmed elsewhere: a Claude Code issue documents the agent running git reset --hard on session startup, destroying unpushed commits.

The same week, a Cline issue documented the agent force-pushing during a merge conflict, overwriting their own remote work and erasing live website templates. A separate Claude Code thread reported the agent running git filter-repo --strip-blobs-bigger-than 500K --force, deleting four production files from every commit in the repository’s history.

These aren’t outliers. There are at least a dozen open issues across anthropics/claude-code, cline/cline, and cursor forums describing variants of the same pattern: agent decides to “clean up,” runs a destructive git command, history disappears.

What an agent has the power to do in your repo

Modern AI agents run shell commands as the user that launched them. If you’ve authenticated git push once in your terminal, the agent can git push --force. If you’ve configured a remote, the agent can rewrite that remote’s history. The model doesn’t know which commits are months of teammate work and which are scratchpad — it’s all just hash strings to a token predictor.

The destructive git verbs that need a gate:

  • git reset --hard — destroys uncommitted work AND moves the branch pointer
  • git push --force (and --force-with-lease) — overwrites remote history
  • git clean -fd (or -fdx) — deletes untracked files
  • git filter-repo / git rebase -i — rewrites historical commits
  • git checkout . / git restore (with no path) — discards working-tree changes
  • git branch -D — force-deletes a local branch

Any of these, called at the wrong moment, costs work that doesn’t restore from git reflog if the reflog itself has been touched.

Irreversible actions live one tool call away

Git history rewrites are a special class of irreversible: technically the data is recoverable from the reflog or unreachable objects for ~30 days… if those caches haven’t been pruned, if the agent didn’t run git gc --prune=now, if the user is sophisticated enough to know what to look for. In practice, most devs treat a force-push or hard-reset as final.

The Cline force-push incident specifically destroyed a production website template. The Claude Code startup-reset incident destroyed unpushed commits. The git-filter-repo incident destroyed historical context across the whole project. None of these were recoverable in a way that didn’t require expensive forensics.

The probability of one of these in your repo this quarter — across your team, across your AI agent fleet — is not low.

Three steps that put a gate between your agent and git

Step 1 — Install the hook

For Cursor, Claude Code, or Codex CLI:

curl -sf https://agenticcontrolplane.com/install.sh | bash

Every shell command the agent runs goes through ACP’s PreToolUse hook before execution. The model can’t route around it — Cursor / Claude Code / Codex enforces the hook from the host side, regardless of what the model wants.

Step 2 — Deny destructive git verbs by default

ACP classifies Bash sub-commands by binary, so git-prefixed calls land under Bash.git. You can be more granular by writing pattern rules within Bash.git for specific argument shapes:

{
  "mode": "enforce",
  "tools": {
    "Bash.git": {
      "background": {
        "permission": "deny",
        "patterns": [
          { "match": "git push.*--force", "permission": "deny" },
          { "match": "git push.*-f\\b",   "permission": "deny" },
          { "match": "git reset.*--hard", "permission": "deny" },
          { "match": "git clean.*-[fd]",  "permission": "deny" },
          { "match": "git filter-repo",   "permission": "deny" },
          { "match": "git branch.*-D",    "permission": "deny" }
        ]
      },
      "interactive": {
        "permission": "ask",
        "patterns": [
          { "match": "git push.*--force",  "permission": "ask" },
          { "match": "git reset.*--hard",  "permission": "ask" },
          { "match": "git filter-repo",    "permission": "ask" },
          { "match": "git rebase -i",      "permission": "ask" }
        ]
      }
    }
  }
}

The semantic: in background tier (cron-scheduled jobs, headless agents, anything running without you watching), destructive git operations are denied outright. In interactive tier (your live Cursor / Claude Code session), they require an explicit approval — you’ll see an ask prompt in the dashboard before the call executes.

That single rule eliminates the class of “the agent rewrote my history overnight” incidents.

Step 3 — Bind end-user identity, per request

If you’re running an agent server-side (a CI integration, a webhook handler, a fleet of background agents), bind the end-user’s identity instead of the service account’s:

from acp_governance import set_context

@app.post("/run")
def run(req, authorization: str = Header(...)):
    set_context(
        user_token=authorization.removeprefix("Bearer ").strip(),
        agent_name="repo-maintenance",
        agent_tier="background",
    )
    return run_agent(req)

ACP’s policy engine resolves permissions against the user’s IdP scopes. If the user doesn’t have git:force-push on the protected branch in their RBAC, the agent doesn’t either — even if the underlying credential technically allows it.

This matters for a different reason in git workflows: branch protection rules on the Git server (GitHub / GitLab) enforce push rules at the receive-pack layer, but they don’t enforce intent. ACP enforces intent at the call site, before the request even leaves your machine.

(Free fourth step) — Audit log

Every governed git operation writes a structured row regardless of decision: command, repo, branch, agent identity, tier, decision, reason, timestamp. The denied force-push is logged the same way the allowed merge is. When something rolls back oddly, you have answers, not guesses.

The total time investment

  • One curl command (Step 1): ~30 seconds
  • Six policy rules in the dashboard (Step 2): ~3 minutes
  • One line in your handler (Step 3, optional for server-side agents): ~1 minute

Three to five minutes from blank slate to “an autonomous agent in this environment cannot rewrite history without an explicit policy override and an interactive confirmation.”

If you’ve ever had to explain to your team why their commits disappeared, three minutes of work removes the part where you have to explain it again.

Git verbs are one family in the surface. The Tool Surface Index has the rest, grouped by blast radius — and if you run Claude Code or Codex daily, ACP for coding agents is the one-command version of everything above.

Correction (2026-07-18): An earlier version of this post presented the Hacker News reset-loop report as a confirmed Claude Code failure. The reporter later retracted that attribution after tracing the resets to their own GitPython polling tool; the anecdote now reflects the retraction, alongside the separately confirmed startup-reset case (anthropics/claude-code#34327). We also described the Cline force-push as overwriting “a teammate’s remote work” — cline/cline#7682 involved the user’s own repository, and the wording has been corrected.

AgenticControlPlane.com

Share X HN LinkedIn
Get the next data drop
What agents actually cost, new tool-surface captures, and the occasional incident post-mortem — sent when we publish something worth your inbox, not on a schedule. Unsubscribe anytime.
Share: Twitter LinkedIn
More in Stop Your Agent From…
  1. 1. Stop your AI agent from running `rm -rf` on your filesystem — in three steps
  2. 2. Stop your AI agent from deleting your production database — in three steps
  3. 3. Stop your AI agent from touching files outside your project — in three steps
  4. 4. Stop your AI agent from rewriting your git history — in three steps · you are here
  5. 5. Stop your AI agent from leaking secrets in your `.env` file — in three steps
  6. 6. Stop your AI agent from leaking PII through tool calls — in three steps
  7. 7. Stop your AI agent from burning through your API budget — in three steps
  8. 8. Stop your AI agent from making payments without approval — in three steps
  9. 9. Stop your AI agent from dropping a Kubernetes namespace — in three steps
  10. 10. Stop your AI agent from escalating IAM permissions — in three steps
  11. 11. Stop your AI agent from being weaponized by a malicious package — in three steps
Related posts

← back to blog