◉CLAUDE LABJP
●2.1.310 — Monitor tool now works on Bedrock, Vertex and Foundry, and gateway failures are fixed (10/23)●2.1.307 — Fixes MCP failing to reconnect after sleep or a keychain lock (10/21). 2.1.306 also fixed the false claude -p login message●SONNET 4.5 — Retires on the Claude API on 11/30, 36 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●API — In API version 2026-10-21, passing temperature, top_p or top_k returns a 400 error●NEW — I Put a Ten-Line Promise File at the To…●2.1.310 — Monitor tool now works on Bedrock, Vertex and Foundry, and gateway failures are fixed (10/23)●2.1.307 — Fixes MCP failing to reconnect after sleep or a keychain lock (10/21). 2.1.306 also fixed the false claude -p login message●SONNET 4.5 — Retires on the Claude API on 11/30, 36 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●API — In API version 2026-10-21, passing temperature, top_p or top_k returns a 400 error●NEW — I Put a Ten-Line Promise File at the To…
Articles/Claude Code
⟐ Claude Code/2026-06-10Intermediate

When git add -A Sweeps Up .bak Backups — The Quiet Trap of In-Place Fix Scripts

How .bak backups left by sed -i and homegrown --fix scripts get silently swept into production by git add -A, why it is so easy to miss, and how scoped adds and .gitignore close the gap for good.

troubleshooting99error17fix12git18claude-code132automation109

One morning I was glancing over the diffs across three repositories and noticed a file I did not recognize sitting in each of them: next.config.ts.bak. Tracing the commit history, I found that the previous night's redirect-integrity pass had committed and pushed that backup right alongside the real .ts file.

The build was green. The sites were serving. And that is exactly why nothing stopped it.

I have been an indie developer since 2014, running apps and blogs in parallel, and at Dolice Labs I update four technical blogs every day through scripts driven by Claude Code. As you automate the "mechanical but scary if it breaks" chores — formatting, link fixes — this kind of silent contamination becomes the real hazard. Mistakes you make by hand are easy to catch. The junk that automation carries along, quietly, is not.

What actually slipped in — the backup that --fix leaves behind

The trigger was a homegrown integrity checker. It inspects whether redirects for deleted articles collide with the MDX of articles that have since come back, and I had it auto-correcting with a --fix flag.

Before rewriting, the script saves the original next.config.ts as next.config.ts.bak in the repo root. A safety backup. The problem was the commit step that ran right after it.

# What the tail of the pipeline was doing
python3 redirect_integrity.py "$REPO" --fix
git add -A          # ← this sweeps up the .bak too
git commit -m "Fix redirect integrity"
git push origin main

git add -A stages every change in the working tree. The freshly created next.config.ts.bak is, of course, a "new file," so it gets added with no warning at all. A .bak is harmless to a Next.js build, so neither CI nor Cloudflare lights up red. The result: backup files took up residence in three repos — gemilab, antigravitylab, and rorklab.

Why it is so hard to notice

The essence of this bug is that nothing breaks.

One extra .bak file, and the site still renders fine. Tests still pass. No error appears in the logs. The repo just gets slightly dirtier, and that dirt does not clean itself up — it only gets overwritten the next time the same --fix runs. It accumulates, quietly.

Worse, a .bak is almost identical to the original, so even staring at a diff later, it rarely jumps out as "wait, what is this?" I only caught it because I happened to be comparing diffs across three repos at once and saw the same filename lined up three times.

In automation, an exit code of 0 and a green log do not mean "correct." The fact that git push raised no error and the fact that the repository matches your intent are two different things. Seen as the same class of "silent failure," git push reporting success while nothing reaches the remote shares the same root: in automation, the green log is precisely the thing to distrust.

Reproducing it

You can reproduce it in a few seconds.

mkdir -p /tmp/bak-demo && cd /tmp/bak-demo
git init -q
echo "export const config = {}" > next.config.ts
git add next.config.ts && git commit -qm "init"
 
# A classic in-place tool that leaves a .bak
sed -i.bak 's/{}/{ trailingSlash: true }/' next.config.ts
ls
# -> next.config.ts  next.config.ts.bak
 
git add -A
git status --short
# A  next.config.ts.bak   <- staged without you asking
# M  next.config.ts

sed -i.bak is a common form on macOS, and it always leaves a .bak. GNU sed's sed -i (no suffix) makes no backup, but I genuinely see sed -i '' on BSD/macOS mistyped into sed -i.bak in the field all the time. Homegrown --fix scripts often write a backup for safety too, and they step on the same trap.

The fix — decide what to stage by addition, not subtraction

The most reliable move is to stop using git add -A and instead list exactly what you want to commit.

# Before: everything, including the .bak
git add -A
 
# After: add only the paths you are allowed to touch
git add next.config.ts content/
git status --short   # eyeball it for strays
git commit -m "Fix redirect integrity"

For a blog-update pipeline, name only the directory you generate or edit, like git add content/. For a process that touches a config file, write that filename directly. "Add everything, then remove the extras" causes far more accidents than "add only what you need."

If you have a real reason to keep git add -A, clean up the backup right after the fix tool and before staging.

python3 redirect_integrity.py "$REPO" --fix
rm -f "$REPO"/next.config.ts.bak   # remove the saved copy first
git add -A

Preventing recurrence — close it structurally with .gitignore

Cleanup and scoped adds are "breaks the moment you forget" rules. A human turning the crank by hand can remember; a pipeline that runs every day will quietly relapse the first time the rule slips. The proper fix is structural.

Put the backup extensions into the repo's .gitignore, and they will never be staged again, even if you use git add -A.

# Backups left by in-place fix tools
*.bak
*.orig
*.tmp
*~

Drop the same block into all four repos, and no matter which script leaves which backup, it is excluded from commits automatically. If you have ever wrestled with how ignore rules interact with file lookups, Claude Code reporting that a .gitignore'd file cannot be found is worth reading alongside this.

As an extra layer, add a guard that mechanically rejects "unexpected files" after staging and before commit. Dropping these few lines at the tail of the pipeline lets you halt the moment a saved file like a .bak sneaks in.

# Right before commit: abort if any backup is staged
if git diff --cached --name-only | grep -qE '\.(bak|orig|tmp)$|~$'; then
  echo "Stray backup file is staged. Aborting commit."
  git diff --cached --name-only | grep -E '\.(bak|orig|tmp)$|~$'
  exit 1
fi
git commit -m "Fix redirect integrity"

The less human oversight a process has, the more this kind of final checkpoint earns its keep. Since adding these lines, I have not repeated the same contamination once.

A .bak that already slipped in will not vanish just because you added it to .gitignore. You have to stop tracking it separately.

git rm --cached next.config.ts.bak   # keep history, drop tracking
git commit -m "Remove stray backup file from tracking"

Make "additive add" the default for automation

This contamination grew out of a single way of writing one commit command. git add -A is convenient when you are working interactively, but in an automated pipeline with no human in the loop, I believe an explicit git add <path> should be the default. Even when you wire up something that lets Claude Code generate commit messages, you want to keep your own hand on what gets staged (I touch on that design in operating auto-generated commit messages).

Automation faithfully repeats the right steps. By the same token, it faithfully repeats the wrong ones. The one decision I want to keep in my own hands, not hand to the machine, is what actually goes into the repository.

I hope this helps anyone wrestling with the same problem.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⟐ Claude Code2026-05-02
Two Reasons Claude Code's Edit Tool Refuses — Multiple Matches vs. No Match
Claude Code's Edit tool fails in two distinct ways: the string matches in several places, or it matches nowhere. Here is how to tell them apart, how to build a good old_string, and how to check for formatters, line endings and invisible characters.
⟐ Claude Code2026-05-24
When Claude Code's Sequential Edits Cascade into Failure — Fixing 'old_string not found' and 'not unique' on the Same File
Run several Edits against the same file and the second or third one suddenly fails with 'not found', 'not unique' or 'modified since read'. Here is how to tell the three apart, a small script that counts matches first, and the correct formatter hook setup.
⟐ Claude Code2026-08-25
A One-Letter Typo in settings.json Is Ignored Without a Single Warning
I diffed claude doctor output between a settings.json with misspelled keys and a correct one. There was no difference at all. Here is what actually gets validated, what slips through, and a small check that catches typos before they cost you a day.