How to Stop an AI Coding Agent From Regressing Your Production Site
An AI agent nearly deployed correct code from a stale branch, silently reverting weeks of shipped work. Here is the 3-line git guard that stops it.
Why this matters
The scariest AI agent deploy failure is not bad code. It is correct code from a stale branch that quietly erases work you already shipped. Three git checks run before every push fix this permanently.
My AI agent was one command away from erasing two weeks of work on my live site. The code it wanted to deploy was correct. That was the problem.
The branch was clean. The tests passed. The diff looked reasonable. The only thing wrong was that the branch was 100 commits behind origin/main. Deploying it would have silently reverted every change I had already shipped since I cut that branch.
I caught it because I run one check the agent did not think to run. This post explains that check, why it is easy to miss, and what else I caught in the same afternoon.
How do you stop an AI coding agent from regressing your production site?
Compare the deploy branch to origin/main directly before every push. Do not check if the branch is clean. Do not check if it is ahead of its own upstream. Check the exact command: git rev-list --count origin/main..HEAD (commits your branch is ahead of main) and git rev-list --count HEAD..origin/main (commits you are behind). Block the deploy if the second number is nonzero. That is the whole guard.
The problem: you delegated deploys, and that created a blind spot
Running an AI coding agent to deploy your site feels like an upgrade. The agent can run the build, check for errors, push the code, and verify the deployment. You go from clicking a dozen things to reviewing a summary.
But agents follow the instructions you give them. If you tell an agent to “deploy the current code,” it will do exactly that. It will check git status. It will see a clean working tree. It will run the build. It will push. It will not compare your branch to origin/main unless you specifically wrote that into the deploy script.
That gap is where the stale-branch failure lives.
The agent is not doing anything wrong. The code on the branch is correct for the task it was solving. The problem is the framing: “current code” means “what is on this branch,” not “what is current relative to everything we have ever shipped.”
The agitation: this failure is invisible to every normal check
Here is what makes this failure mode genuinely nasty. Every standard signal tells you things are fine.
git status shows a clean working tree. There are no uncommitted changes. No red text.
The test suite passes. The code on the branch does exactly what it was written to do.
The diff looks correct. If you review the branch diff, you are reviewing the delta from the branch point. That delta is fine.
The build succeeds. No type errors, no compilation failures, no prerender crashes.
The agent sees all of this and proceeds. So would you, if you were not explicitly looking for the one signal that actually matters: how far behind origin/main is this branch?
The only way to catch a stale-branch deploy is to compare branches directly. No other check surfaces it.
The second trap: the “ahead N” misread
There is a related failure that compounds this. When your agent reads git output, it may see something like:
Your branch is ahead of 'origin/feature-branch' by 3 commits. That reads as safe. The branch is ahead. We are not behind. Ship it.
But “ahead” is relative to the branch’s own upstream, which is origin/feature-branch. That upstream could itself be 97 commits behind origin/main. The message tells you nothing about whether you are current relative to main.
I hit this exact case while writing this post. The branch I was working from reported itself ahead of its own upstream. A direct check showed it was 104 commits behind origin/main and 63 ahead. Both numbers were true. Only the first one was dangerous.
The fix: never compare to the branch upstream. Always compare to origin/main. Those are different things. Your deploy script should not know or care what the branch’s own upstream says.
The solution: three guards, one deploy script
After this incident I added three checks to my pre-deploy script. Each one catches a different failure mode.
Guard 1: how far behind main are you?
BEHIND=$(git rev-list --count HEAD..origin/main)
if [ "$BEHIND" -gt 0 ]; then
echo "Branch is $BEHIND commits behind origin/main. Rebase or merge before deploying."
exit 1
fi This is the primary guard. It is the one that would have caught the 100-commit incident. If you add nothing else, add this.
Guard 2: how far ahead are you?
AHEAD=$(git rev-list --count origin/main..HEAD)
if [ "$AHEAD" -gt 30 ]; then
echo "Branch is $AHEAD commits ahead of origin/main. Review before deploying."
exit 1
fi This one is softer. A large ahead count is not necessarily wrong, but it warrants a look. A feature branch that has drifted 60 commits ahead of main without merging is probably carrying stale assumptions somewhere.
You can tune the threshold. I use 30 because that is roughly two weeks of work on my cadence. Adjust for yours.
Guard 3: a blocking build gate
npm run build 2>&1
if [ $? -ne 0 ]; then
echo "Build failed. Fix before deploying."
exit 1
fi This sounds obvious. It is not always obvious in practice. After fixing the stale-branch issue above, I introduced a duplicate YAML key in a frontmatter block during the fix. The site’s prerender step found it and threw a 500. That 500 would have reached production if the build gate was not in front of the deploy step.
Fix-introduced regressions are real. The guard catches them.
Results: what these three checks caught
These are the actual failures from the 2026-05-30 incident that prompted this post.
| Failure mode | What it looked like | Why “the code is fine” hid it | The guard |
|---|---|---|---|
| Stale-branch deploy | Branch was 100 commits behind origin/main; deploy would have reverted two weeks of shipped work | git status showed clean; tests passed; the diff looked correct | Pre-deploy git rev-list --count origin/main..HEAD and HEAD..origin/main — block if behind |
| “Ahead N” misread | “Your branch is ahead 3 of origin/feature” read as safe | “Ahead” is vs the branch’s own upstream, not origin/main | Always diff against origin/main, never the branch upstream |
| Fix-introduced regression | A duplicate YAML key added during the fix 500’d the prerender | The fix itself was untested before deploy | A blocking build/prerender gate in front of every push |
Numbers are from the real incident: 100 commits behind, one near-revert avoided, one prerender 500 caught before publish.
The incident walk-through
Here is the actual sequence from 2026-05-30.
I was consolidating two near-duplicate posts on my site and wiring up a redirect. The work was on a feature branch that was several weeks old. When it came time to deploy, the branch looked ready: clean tree, passing checks, a reasonable diff.
Before pushing, I compared the branch to origin/main directly. It was 100 commits behind. Deploying it would have reverted weeks of shipped posts. There was no script protecting me at that point. I caught it by hand, because stale branches had burned me before.
So I did not deploy from that branch. I checked out a clean worktree off origin/main, replayed only my intended change onto it, and deployed from there. During that replay I introduced a duplicate YAML key in a post’s frontmatter. The build’s prerender step threw a 500 and refused to continue. I fixed the key and rebuilt clean before anything went out.
Two potential failures that afternoon. Zero production regressions. That experience is what turned a manual habit into the three-guard script above.
Where agents get this wrong structurally
This is not a criticism of AI coding agents. It is an observation about how they use git.
Agents are excellent at following a plan. They run commands, read output, and take the next step based on what they see. The problem is that the commands they run by default do not include the comparison to origin/main. Nobody taught them to run it.
The agent’s mental model of “safe to deploy” is built from signals like git status, test output, and build output. Those signals are all local. They are relative to the branch, not to the production history.
A human developer who has shipped a regressions this way once will internalize the origin/main check. They have felt the pain of deploying stale code. The agent has not. So you have to encode that institutional knowledge into the deploy script, where it runs automatically every time.
That is the broader lesson. The guardrail is not a workaround for a bad agent. It is how you give the agent the context it cannot derive from git output alone.
What about worktrees?
Worktrees are worth a mention here. If your agent does file editing work in a git worktree, the worktree and the main repo may be on different branches. The worktree’s HEAD and the main checkout’s HEAD are separate.
If you are deploying from a worktree, make sure the pre-deploy script runs git fetch origin first and then compares to origin/main, not to main. A local main branch that has not been fetched recently will give you a false-clean comparison.
On my own site, the deploy always runs from a worktree checked out directly at origin/main, never from whatever feature branch I happened to be editing in. That one habit makes the stale-branch deploy structurally impossible. The worktree starts current by definition, and my only job is to replay the intended change onto it.
The fetch-first pattern:
git fetch origin
BEHIND=$(git rev-list --count HEAD..origin/main) One extra command. It eliminates the stale-fetch false-negative.
Putting it together: the full pre-deploy script
Here is the complete script, combining all three guards.
#!/bin/bash
set -e
# Fetch latest from origin
git fetch origin
# Check how far behind main we are
BEHIND=$(git rev-list --count HEAD..origin/main)
if [ "$BEHIND" -gt 0 ]; then
echo "ERROR: Branch is $BEHIND commits behind origin/main."
echo "Rebase or merge before deploying."
exit 1
fi
# Warn if far ahead of main
AHEAD=$(git rev-list --count origin/main..HEAD)
if [ "$AHEAD" -gt 30 ]; then
echo "WARNING: Branch is $AHEAD commits ahead of origin/main."
echo "Review before proceeding."
read -p "Continue? (y/N) " confirm
if [ "$confirm" != "y" ]; then exit 1; fi
fi
# Blocking build gate
echo "Running build..."
npm run build
echo "All pre-deploy checks passed." Drop this in your project root, make it executable, and add it to your deploy command or your agent’s instructions for deploy tasks.
Is this about AI agents specifically?
Not entirely. A human developer running git push without checking origin/main can cause the same regression. But humans tend to accumulate the intuition over time. They get burned once and start doing the check.
Agents do not accumulate intuition across sessions. Every deploy attempt starts from the same baseline understanding. Which means the check has to be structural. It has to live in a script or a CI step, not in the agent’s memory.
That is the key insight here. AI agents do not have a bad day where they remember to do the extra check. They also do not have a bad day where they forget. They execute what is written. Write the guard into the script, and the agent will run it every time.
This is one of those cases where the AI’s consistency is actually an advantage, once the right behavior is encoded.
The broader pattern: guards over memory
The three guards above follow a pattern that applies beyond git. The pattern is: encode safety checks as executable code, not as instructions you give to an agent.
An instruction like “make sure the branch is current before deploying” is easy to forget, easy to misinterpret, and impossible to audit. A script that runs git rev-list --count HEAD..origin/main and exits nonzero on failure is deterministic, repeatable, and auditable.
Any time you find yourself writing an agent instruction that says “always check X before doing Y,” ask whether X can be encoded as a gate instead. Usually it can.
This is how you build systems that do not require the agent to hold institutional memory it does not have.
The citability bridge
I build guardrails like this because most of what breaks a site is invisible by eye. Stale-branch deploys are invisible to git status. The same principle applies to AI visibility.
Most site owners assume that if their content is indexed and their site loads fast, Claude, Perplexity, and ChatGPT can see and cite it. Often they cannot. Structured data gaps, ambiguous entity signals, and content framing that reads fine to humans but confuses AI retrieval systems are all invisible without a dedicated audit.
That is what citability.dev measures. A free 3-minute scan shows you where your site stands. The $199 full audit gives you the full picture: what AI systems can cite, what they skip, and exactly what to fix.
If you are building in public or running a site you care about, it is worth knowing whether the AI layer can actually see it.
Free scan at citability.dev. Full audit at $199.
Sources & Further Reading
Further Reading
- I Added WebMCP to SvelteKit: 90 Min, 3 Files. Build WebMCP into SvelteKit apps using navigator.modelContext. Learn polyfill setup, tool schemas, and verification in 2026.
- Claude Code Hooks Caught a Secret Leak Before I Shipped It Stop accidental secret leaks in Claude Code hooks. Learn 4 production patterns to validate, format, and gate commands before execution.
- I Run Python Agents on a $6/Month DigitalOcean Droplet Deploy Python agents on DigitalOcean for $6/month. Skip serverless complexity, avoid setup gotchas, run async bots reliably.
What do you think?
I post about this stuff on LinkedIn every day and the conversations there are great. If this post sparked a thought, I'd love to hear it.
Discuss on LinkedIn