Your AI agent re-adds code you reverted last month A developer created revert_guard.py, an offline pre-commit gate that blocks AI agents from re-adding code that was previously reverted in a git repository. The tool reads the repo's revert history and exits with an error if a diff reintroduces a previously reverted entity, preventing issues like the one where a Claude Code session re-proposed a PCI-DSS-scoped column that had been reverted a month earlier. AI agent re-adds reverted code when a fresh session, with no memory of last month's decision, re-proposes it. revert guard.py is an offline, keyless pre-commit gate: it reads your repo's own git revert history and blocks exit 1 a diff that reintroduces a column the team already added and reverted, before the commit lands. AI disclosure:I wrote revert guard.py with an AI assistant and ran it myself, offline, before publishing. Every output block below is pasted from a real local run on Python 3.13.5 and git 2.50.0, standard library only, no network. I ran each scenario twice and confirmed the stdout was byte-for-byte identical; the tool also prints a sha256 digest of its own report so you can check. The card token / PCI-DSS story and the Selvedge fix are @masondelan 's, reported on Dev.to; that is their case and their fix, not my measurement. My exit codes, hashes, and the 2026-06-05 revert are synthetic fixtures on a real git repo, each from a real run, and I keep them in their own paragraphs so the two never blur. In short: card token column, and helpfully proposes adding one. The reasoning that killed it the first time a PCI-DSS scope call is gone. The revert that killed it is still sitting in git log . users.card token , not by file path, so a fresh migration number does not slip past. users.card token . Point it at repo clean and it exits 0 SHIP . Point it at repo dirty , where that column was added and reverted, and it exits 1 BLOCK and prints reverted 2026-06-05 in c2ce7ed -- reason: "PCI-DSS scope" . The only variable is the repo's revert history. git log / git show on read, never writes, never runs the agent. Exit 0 / 1 / 2 for a CI gate. Deterministic stdout with a self-hash. The tool and every fixture are in this post.On July 6, an engineer posting as @masondelan https://dev.to/masondelan wrote up an incident on Dev.to that I have not stopped thinking about. Their line for it: the code sticks around, the reasoning doesn't. A team had added a card token column to their users table, then reverted it two days later because it pulled the table into PCI-DSS scope. About a month on, a fresh Claude Code session, working from the current schema with none of that history in context, planned the exact same column back in. Their fix was a runtime MCP server called Selvedge that answers prior-attempts users.card token with something like "Prior attempt 28 days ago reverted after 2 days ." Those numbers and that fix are theirs. I am borrowing the shape of the problem, not the measurement. Here is the shape. The revert is not lost. It is a commit, in the log, with a message. Git is tracking the fact that the team said no. What git does not do is stop the next actor from proposing it again. A human reviewer might remember. A fresh agent session will not, and neither will a tired reviewer at 6pm looking at a diff that, on its own, looks completely reasonable. The information exists and nothing acts on it. That gap between "the repo knows" and "something enforces it" is the whole space this tool sits in. So the tool turns the tracking into control at one specific moment: before the diff is committed. It does not need the agent's memory, a vector store, or a running service. It needs the history the team already keeps. Three verdicts, one rule, read off the repo's own reverts. users.card token , exactly equals one a revert commit removed. That is a BLOCK. It prints the revert's short hash, date, and the reason the commit message stated. card token in a model file is definitely the reverted users.card token .The distinction the whole thing turns on: ALTER TABLE users ADD COLUMN card token gives a qualified entity, because the table is right there on the line. A lone card token = Column ... in a model gives a bare one, because nothing on that line says which table. Two qualified entities have to match table and column to BLOCK. A bare one on either side can only ever reach WARN. Same column name, different confidence, different verdict. No keys, no network, no install past Python and git. Save the file, point it at a proposed diff and a repo, run one command. Here is the whole tool, one file, standard library only: bash /usr/bin/env python3 """ revert guard.py -- an offline pre-commit gate that reads a repo's OWN git revert history and blocks exit 1 an AI agent's proposed diff that reintroduces a column / symbol / flag the team already added and then REVERTED -- before the diff is committed. It takes a proposed change a unified diff, or a JSON list of entities plus a --repo path. It shells out to a LOCAL git log / git show on READ only, finds the commits whose message marks them as a revert, extracts the entities those reverts removed, and intersects that set with the entities the proposed diff adds. The match is by ENTITY a column name, table-qualified when it can be: users.card token , never by file path -- so a brand-new migration file with a different number is still caught. REINTRODUCES REVERTED -- a table-qualified entity in the diff e.g. users.card token exactly equals one a revert commit removed. BLOCK. Prints the revert's short hash, date, and stated reason. NAME MATCH UNVERIFIED -- the bare name matches a reverted entity but one side is unqualified, so it cannot be confirmed the SAME table's column. WARN, fail-closed a human verifies . NO REVERT MATCH -- nothing the diff adds was ever reverted here. SHIP. BAD INPUT -- not a git repo, unreadable/empty diff, bad JSON. The point the tool exists to make: take ONE proposed diff -- a new migration that adds users.card token -- and run it against two repos. On a repo that never reverted that column it exits 0 SHIP . On a repo where the same column was added and reverted last month it exits 1 BLOCK , and prints the prior revert. Same diff, same agent; the only variable is whether the REPO remembers the revert. This is not about the cost of agent memory and not about permissions -- the agent re-proposes what the team already reverted, because the reasoning died with the session, while the revert did not. Offline. Keyless. Read-only. Zero network. Standard library only subprocess for git on read, sys, re, json, hashlib, argparse . It never writes, never commits, never runs the agent, never touches the network, and reads the diff as text -- it is DATA, never executed. Output is byte-for-byte deterministic across runs on the same repo; it prints absolute revert dates not "N days ago" on purpose, so the output does not change with the calendar, and ends with a sha256 digest of its own report so two runs are provably identical. It does NOT store memory or embed reasoning; it reads git history the team already keeps. It does NOT decide who is allowed to change what. It does NOT understand WHY: it matches names, not intent, so a column reverted for reason X and now legitimately needed for reason Y is still flagged for a human to override. It only sees reverts that are actually COMMITTED -- a revert done by force-push, squash, or amend-out-of-history is a blind spot. It is as good as the team's git history is honest. Exit codes usable as a pre-commit / CI gate : 0 SHIP no proposed addition was previously reverted here 1 BLOCK or WARN both mean "do not auto-apply"; the reason-code says which . WARN's exit is configurable via --warn-exit default 1, fail-closed . 2 bad input: not a git repo, missing/unreadable/empty diff, unparseable JSON -- fail-closed. Usage: python3 revert guard.py