In my last post about running two Claude Code accounts side by side, I copied a brownfield project into a fresh directory before mounting it, specifically to keep the AI tool away from an old repo's committed secrets, then cherry-picked the real commits back into the original history. That works, but it's really just avoidance: the secret is still sitting in the original repo's history the whole time, untouched.
There's a more direct option: rewrite history and remove the secret from the repo itself.
The two options I already had
Before reaching for history rewriting, there are two lighter options:
Copy and cherry-pick. What I did last time. No history rewrite, but it's extra ceremony every time you touch that project, and the secret is still there, waiting, for anyone with an old clone or repo access.
Rotate and move on. Simplest option: treat the committed secret as burned, issue a new one, and let the old value sit in history, powerless. No git surgery at all. Fine when rotating is cheap and you don't care about the old value lingering in the logs.
Option three: rewrite history
Instead of avoiding history (copy) or accepting it's fine (rotate), you can actually purge the secret from history itself, in the real repo:
# using git filter-repo (recommended over the older BFG/filter-branch)
git filter-repo --path appsettings.Development.json --invert-paths
# or, to replace values rather than remove the file entirely:
git filter-repo --replace-text secrets-to-replace.txt
The first form drops a whole file from every commit that ever touched it. The second scrubs specific string values wherever they appear, useful when the file itself needs to stay but one value in it doesn't.
The trade-offs
This is the most invasive of the three options, worth being upfront about that:
- It rewrites every commit SHA downstream of the change. This isn't a normal commit, it's history surgery. Anyone else with a clone needs to re-clone or hard-reset, not just pull.
- It's irreversible on shared history once others have pulled the old commits, unless you force-push and everyone re-syncs. Fine for a solo brownfield project, riskier on a team repo with active branches.
- Still rotate anyway. Purging history doesn't undo the fact the secret was live and committed for however long. Anyone with an old clone, a CI cache, or repo access during that window could already have it. Same rule as before: if it was ever committed, treat it as compromised.
Three tiers now
So really there are three tiers, not two:
- Copy and cherry-pick. No history rewrite, extra per-session work, secret still lives in history.
- Rotate and mount normally. Simplest, accepts the exposure is moot since the old value is dead.
- Rewrite history. Cleans the actual repo permanently, but it's the heaviest option, and it doesn't replace rotation, only adds to it.
For a solo brownfield repo like the one in my last post, option three is probably the right default going forward: it fixes the actual problem (the secret sitting in history) instead of working around it every session.