Todd Mercer

If an agent session ever runs git checkout <branch> followed by git reset --hard origin/<branch> in your working tree, and your gitignored .env happens to share a path with a file that branch's history once tracked, git will overwrite that .env without asking and then delete it on the reset — with no recycle bin, no confirmation, and often no local backup. This is not a Claude Code bug; it is git's documented default behavior, and it can bite any agent (or human) that runs those two commands back to back. Protect the file by restricting direct reads and writes, requiring review before destructive Git operations, and keeping a backup outside the working tree. The deny rules below reduce direct tool access; the Git command policy is an instruction, not an enforced block on Git side effects.
Next action: install Aident Loadout once, then run a bounded, read-only sandbox check of your own repo before you trust any agent with git reset or git checkout:
Preflight the exact Action, then run this against your own clone: git log --all --oneline -- .env (any output means .env is tracked somewhere in your history, which is the exact precondition for this failure) and grep -c '"deny"' .claude/settings.json (zero means you have no deny rule yet). Both checks are read-only; use the results to decide whether to add the rules below.
The Exact Failure
GitHub issue anthropics/claude-code#104331, filed October 22, 2026 — by Claude Code itself, at the user's explicit request, after the in-app feedback button and browser-based claude.ai login were both unavailable — lays out the full git reflog timeline:
The user asked the agent to connect an existing local workspace to a private GitHub repository, rewrite the README, and set up hosting. They did not ask for any reset, checkout, cleanup, or deletion.
The agent moved the project's
.gitdirectory up to the workspace root, preserving history, and committed. That preserved history happened to track a file at the root path.envfrom an earlier point in the project.The workspace root also held a separate, untracked, gitignored
.envwith the user's live API keys — unrelated to the old tracked copy, just colliding on the same path.The agent opened and merged a pull request into
main, then ran, unprompted, a single chained "sync local main" command:git fetch -q origin && git checkout -q main && git reset -q --hard origin/main && git log --oneline -4.git checkout -q mainsilently overwrote the gitignored.envwith the old tracked version, because the localmainref still pointed at the commit that tracked that path.git reset -q --hard origin/mainthen deleted the file outright, because the newmainno longer tracks it at all.The user noticed about fifteen minutes later. Keys for five separate services — an advertising API, a weather API, a public-data API, a cloud-vision API, and an LLM API — were gone, with no editor local history, no Time Machine backup, no shell-history trace, and no agent-transcript record of the values. The user had to regenerate every key from scratch.
The repository's own triage labeled the report data-loss, area:security, and area:permissions on filing. As of this writing the issue is open with no maintainer response yet, so treat the underlying git behavior — not a pending patch — as the thing to defend against.
Why Git Does This on Purpose
Neither step is a malfunction. Both are documented, long-standing git behavior that predates agentic coding tools entirely:
git-checkoutdocumentation, under--overwrite-ignore: "Silently overwrite ignored files when switching branches. This is the default behavior. Use--no-overwrite-ignoreto abort the operation when the new branch contains ignored files." Git's untracked-file safety check — the one that normally stops a checkout from clobbering your work — explicitly does not cover ignored files. A gitignored file sitting at a path the target branch tracks is treated as expendable.git-resetdocumentation, under--hard: "Overwrite all files and directories with the version from<commit>, and may overwrite untracked files. Tracked files not in<commit>are removed so that the working tree matches<commit>." Once the checkout step above has made.envtracked again (by overwriting it with the old tracked copy), a subsequentreset --hardto a commit where.envisn't tracked removes it as part of making the working tree match.
The trap only fires when both conditions line up: your currently gitignored secret file shares a path with something some reachable commit once tracked, and something runs checkout followed by reset --hard without pausing. That combination is easy to hit by accident the moment a repository's .git directory gets moved, restructured, or re-rooted — exactly what the agent was doing in issue #104331 — because restructuring often resurrects old tracked paths that current .gitignore rules were never written to anticipate.
This is distinct from the more commonly discussed failure where an agent intentionally rewrites .env with placeholder values while "cleaning up" a project or filling in a template; that's a model judgment call about file contents. What's described here has nothing to do with judgment — it's git's plumbing doing exactly what the man pages say, triggered by a command sequence that never needed to touch the working tree at all.
The Fix: Four Layers
No single control here is sufficient alone — stack them.
1. Deny the exact tool calls in .claude/settings.json. Claude Code's own settings reference documents permissions.deny with this literal example:
Extend it to cover writes and your real path, and add a matching Bash deny since deny only blocks Claude Code's built-in Read/Write/Edit tools, not a shell command that touches the same file:
Commit this to the project's .claude/settings.json so the whole team shares it; use .claude/settings.local.json only for personal overrides. The built-in tool rules restrict direct access through those tools. The two Bash patterns are best-effort examples for those exact command shapes, not a filesystem boundary: other shell commands can still touch the same path. None of these file rules prevents a git checkout/reset side effect; layer two is a command-review policy, not an enforced Git deny rule.
2. Replace the destructive sync with the non-destructive one. The GitHub issue's own root-cause section names the fix: git branch -f main origin/main updates where the local main ref points without touching the working tree at all, so it can't overwrite or delete anything sitting in it. If an agent (or a script) only needs the local branch pointer to catch up to the remote, that command — not checkout plus reset --hard — is the correct tool. Use git branch -f only for a branch that is not checked out in any worktree, and inspect its local-only commits first: it can discard the branch reference to those commits even though it leaves files alone. If you are already on main, git merge --ff-only origin/main is the normal fast-forward operation, but it updates tracked working-tree files; review and preserve local work before running it. Put the command-review rule in your project's CLAUDE.md or AGENTS.md:
This is a soft guardrail — an instruction file, not an enforced boundary — so treat it as one layer among several, not the whole defense.
3. Keep a copy of your secrets outside the working tree. The single point of failure in issue #104331 was that the live keys existed in exactly one place: an ignored file inside the directory the agent was restructuring. A secret manager, your deploy platform's environment-variable panel, or even a password manager entry gives you a second copy that a local git operation can't reach. If you want agents to read and inject credentials without ever holding or exposing the raw values in the project directory, see our guide to giving AI agents API access without exposing keys.
4. Confirm the deny rule is actually what's blocking the earlier, more common symptom. If you've hit a prompt asking to approve reading .env rather than a deletion, that's a different, already-covered situation — see our guide to the Read(.env) permission prompt for preserving that deny rule without false positives.
If the agent you're scoping also has standing access to connected SaaS tools, not just the local filesystem, the same "least-surprise default, most-destructive outcome" pattern shows up there too; see our AI agent access-control matrix across Slack, Drive, Notion, and Jira for scoping that wider surface.
Recover If It Already Happened
Do these in order; stop as soon as one source has your real values.
git reflog— if any commit in your reflog still has.envtracked with the real values (for example, right after the overwritingcheckoutbut before thereset --hard),git show <that-commit>:.envrecovers it. This only works if you catch it before further commands move the ref further and git garbage-collects the blob.Your editor's local file history — VS Code and most Cursor-based editors keep an in-editor history of saved file versions independent of git; check it for
.envif the file was ever opened and saved in the editor.Your deploy platform's environment-variable panel (Vercel, Netlify, Railway, your cloud provider's secret manager) — these are often the most reliable second copy, since most teams set the same keys there to deploy.
Shell history —
grep -i "API_KEY=" ~/.zsh_historyor~/.bash_historyfor a pastexportor.envpaste.Regenerate any key you can't recover from the above. Treat a destroyed key as compromised-by-default rather than spending more time chasing a copy that may not exist.
Sources and Method
anthropics/claude-codeissue #104331, filed October 22, 2026, self-reported by Claude Code at the user's request, with the fullgit reflogtimeline, environment, and impact.git-checkoutreference,--overwrite-ignore/--no-overwrite-ignore, confirming silent overwrite of ignored files on branch switch is the documented default.git-resetreference,--hard, confirming it removes tracked files absent from the target commit.Claude Code settings reference,
permissions.deny, confirming the exactRead(./.env)/Read(./.env.*)deny-rule syntax used above.
About the author

Todd Mercer
Todd Mercer is a senior engineer with more than 10 years of experience working on social media platforms and large-scale ordering systems. He graduated from the University of Illinois Urbana-Champaign in computer science. At Aident, he covers technical issues, developer tools, and technology topics.



The one tool
for every tool
your agent needs.
Give any AI agent real capabilities in seconds. Connect 27,000+ tools once, skip the setup headache, and let your agents execute.



