Blog / git
A git checkout replaced my .env file: why, and how to stop it
Your .env is ignored, yet checking out an old commit puts an old key in its place, and coming back deletes it. When git does this, and four ways to stop it.
On this page
- What happened in our test
- Why git does this
- Check whether your repo has this problem
- Four ways to stop it
- What doesn't help
- Where Joxo fits
- Frequently asked questions
- Why did git overwrite my .env file when it's in .gitignore?
- Why did my .env file disappear after switching branches?
- How do I check if .env was ever committed?
- How do I stop Claude Code running git checkout without asking?
- Try it with your team
Your .env is in .gitignore, so git leaves it alone. That's what most people believe, and it's true until someone checks out an older commit. If any commit in the repo's history tracked .env, git writes that old copy over yours without asking, and when you switch back it deletes the file. No warning, no conflict, nothing in git status afterwards.
People usually notice when a coding agent does it. An agent asked to "check how this worked last week" runs git checkout on an old commit, comes back, and the app no longer starts because .env is gone or holds a key from last year. We reproduced it with git 2.54.
What happened in our test#
We made a small repo the way many real ones start: the first commit included .env by mistake, the second removed it from git and added it to .gitignore. Then we put a new key in the local file:
git init -b main
echo app > app.txt
echo "API_KEY=old-from-2024" > .env
git add . && git commit -m "first, with .env"
git rm --cached .env
echo ".env" > .gitignore
git add .gitignore && git commit -m "untrack .env"
echo "API_KEY=new-real-key" > .envgit status --ignored showed .env as ignored, as expected. Then:
git checkout HEAD~1
cat .env # API_KEY=old-from-2024
git checkout main
cat .env # No such file or directoryThe checkout replaced the new key with the old one, silently. Switching back to main removed the file, because main doesn't track it. git reset --hard to the old commit did the same: old key in, and after resetting back, no .env at all.
Why git does this#
Git treats ignored files as expendable. It refuses to overwrite a file it doesn't know about when that file is merely untracked, and stops with "The following untracked working tree files would be overwritten by checkout". We checked: with .env not in .gitignore, the same checkout refused and kept the new key. An ignored file gets no such protection: .gitignore tells git the file doesn't matter, so git replaces or removes it whenever the commit you're moving to says so.
So the risk isn't .gitignore. It's history. If .env was ever committed, on any branch, every command that moves your files to that commit can touch it: checkout, switch, reset, restore with a source, merges and pulls that bring in that commit, and checking out someone's branch that still tracks it.
Check whether your repo has this problem#
One command tells you:
git log --all --oneline -- .envIf it prints nothing, no commit ever tracked .env, and these commands won't touch it. If it prints commits, two things are true:
- Moving to any of those commits, or to a branch that still contains the file, can overwrite or delete your local
.env. - The key in those commits is in the repo for anyone who can clone it. Change that key with the provider, whatever else you do.
Run it for every secrets file you keep, such as .env.production or config/secrets.json.
Four ways to stop it#
1. Keep your secrets in a file no commit ever tracked. We added .env.local to .gitignore, put the key in it, and checked out the first commit and came back: .env.local kept the new key the whole time, while .env was replaced and then deleted. If your tools read .env.local (many frameworks do), moving the real keys there is the simplest fix. Check it with git log --all --oneline -- .env.local, which should print nothing.
2. Copy it out before moving around in history. If you're about to look at an old commit yourself, copy the file outside the repo first and put it back after:
cp .env ~/env-backup
git checkout <old-commit>
git checkout main
cp ~/env-backup .envThat's easy to forget, and an agent won't do it unless told to, which is why the other fixes matter more.
3. Make your agent ask before it moves your files. In Claude Code, add ask rules to .claude/settings.json for the git commands that rewrite your working folder:
{
"permissions": {
"ask": [
"Bash(git checkout:*)",
"Bash(git switch:*)",
"Bash(git reset:*)",
"Bash(git restore:*)",
"Bash(git stash:*)"
]
}
}We tested it with an allow rule of Bash(git:*) in the same file, the kind of broad rule people add to stop git prompts. Without the ask rules, Claude Code ran the checkout and the old key replaced ours. With them, it stopped and asked, and .env kept the new key. An ask rule wins over a broader allow, so you can keep allowing git status, git diff and git log without prompts. Expect a question for every branch switch; for most people that's a fair trade.
4. Remove the file from history. The thorough fix is to rewrite history so no commit has .env, using a tool such as git filter-repo. It changes every commit after that point, so everyone has to re-clone or reset their copies, and it doesn't undo the leak: anyone who cloned before still has the old key. Change the key first, then decide whether rewriting is worth the disruption.
What doesn't help#
- Adding
.envto.gitignoreagain. It's already ignored; that's exactly why git thinks it may replace it. git update-index --skip-worktreeor--assume-unchanged. They only apply to files git tracks, and your.envisn't tracked in the current commit.- Telling the agent "don't touch .env". It doesn't touch it. Git does, as a side effect of a command that looks harmless.
Where Joxo fits#
With Joxo connected, an agent's git command that would write over an untracked, ignored .env is held before it runs, and you decide from your phone or desktop. It also never copies secret files into the saves it makes before risky commands, so check git log --all -- .env and move your keys if it prints anything. Setup is one line pasted into your agent. For the other half of the problem, see keep Claude Code from reading your .env file, and for keys in fresh worktrees, git worktrees, .env and node_modules.
Frequently asked questions#
Why did git overwrite my .env file when it's in .gitignore?#
Because a commit in your history still tracks .env. Git treats ignored files as expendable, so when you check out, reset to or merge a commit that has the file, git writes that commit's copy over yours without asking.
Why did my .env file disappear after switching branches?#
The branch you left tracked .env (or you had checked out a commit that did), and the branch you switched to doesn't. Git removes files the new commit doesn't track, ignored or not.
How do I check if .env was ever committed?#
Run git log --all --oneline -- .env. Any output means some commit tracked it: those commits can overwrite your local file, and the key in them should be changed.
How do I stop Claude Code running git checkout without asking?#
Add Bash(git checkout:*), Bash(git switch:*), Bash(git reset:*) and similar entries to permissions.ask in .claude/settings.json. In our test they made Claude Code ask even with Bash(git:*) allowed.