Part of the AI coding agent security checklist
Claude Code deleted my files. What now?
Find out exactly what was deleted before you restore anything, and do it soon: the transcript that records it is deleted after 30 days by default.
Checked against the Claude Code and Git documentation, and the advisories and reports under Sources. Published .
Find out what was deleted, and when.
Claude Code writes every message, tool call and tool result to a plaintext transcript, one file per session, at ~/.claude/projects/<project>/<session>.jsonl. Every Bash command the session ran is in there, with the arguments it actually used, which makes it an audit log you already have. Subagents write transcripts of their own, covered below.
ranwhat reads those files, subagents' included, and lists the actions worth knowing about, deletions and destructive git among them, newest first. It changes nothing.
For the deletions alone, with the session each came from, filter the JSON:
uvx ranwhat watch --days 30 --json | jq '.[]
| select(any(.hits[]; .rule == "fs.destructive" or .rule == "git.destructive"))
| {timestamp, project, session, evidence: [.hits[].evidence]}'session is the transcript's file name and project the folder it sits in. The text report prints local time; --json gives the timestamp exactly as the transcript stored it.
For anything in that last row, search the transcripts yourself. Use the name of what is missing, and include subagent transcripts, which Claude Code keeps in a subagents/ folder beside each session:
grep -l 'old-notes' ~/.claude/projects/*/*.jsonl | xargs -r ls -lt
grep -rl 'old-notes' ~/.claude/projects/*/*/subagents/
grep -oE '.{0,80}old-notes.{0,80}' ~/.claude/projects/<project>/<session>.jsonlThe first lists the sessions that mention it, most recent first. The last prints the text around each mention, which is usually the command that removed it. Reading these files by hand is covered in Claude Code history.
Copy the transcript first
Claude Code deletes transcripts older than cleanupPeriodDays, 30 days by default, and the file snapshots rewind depends on go on the same schedule. Copy the session files you found to somewhere outside ~/.claude before you do anything else. They hold whatever passed through a tool, file contents and command output included, so keep the copy private.
What rewind can and cannot restore.
Run /rewind, or press Esc twice with an empty prompt, pick the prompt that led to the damage and choose Restore code. That covers less than it sounds like.
If the menu offers only Restore conversation, no tracked edit comes after that point and rewind has nothing to give back. The snapshots themselves are pre-edit copies of the files Claude changed, kept in ~/.claude/file-history/<session>/. If Claude edited a file with its file tools before it deleted it with rm, an earlier version may still be there:
Restore from what you have.
What works depends on whether git ever saw the file. The timestamps from step 1 tell you which state you want back.
Tracked files
git restore puts paths in the working tree back from a restore source: the index by default, or a commit you name with --source.
git status which tracked files are gone git restore src/app.py from the index git restore --source=HEAD~1 docs/guide.md from a commit
git restore . brings back everything under the current directory, and also throws away unstaged edits to tracked files there. With --source and a directory, as in git restore --source=HEAD~1 docs/, it does the same under that path, and also removes tracked files there that the commit did not have. Read git status first.
After git reset --hard
reset --hard overwrites the working tree with the commit it moved to and removes tracked files that commit does not have. The commits it moved away from are still there: the reflog records where HEAD and each branch pointed before.
git reflog HEAD@{1} is where HEAD was one move ago git branch rescue HEAD@{1} keep that state on a branch, then look git fsck --lost-found dangling objects, staged content included
Reflog entries the current tip can no longer reach expire after 30 days by default, the rest after 90. Uncommitted edits that the reset overwrote were never in a commit, but if they had been staged with git add, git stored their content as blobs. git fsck --lost-found writes dangling blobs into .git/lost-found/other/ as their contents.
Untracked files
git clean removes files git does not track, and rm on a file git never saw leaves git nothing to restore. Outside git, the copies are a backup, a filesystem snapshot or a synced folder's version history. Pick the newest one taken before the timestamp step 1 gave you.
One more place to look. If the file's contents passed through a tool during a session, because Claude read it, wrote it or printed it, they are in that session's transcript, as a JSON string with newlines escaped as \n:
If the agent removed .git itself, the history is gone from this machine. A remote still has whatever was pushed to it: clone it again beside the damaged copy, and move files across by hand.
Deleted in the cloud
Git, rewind and your backups cover this machine. A command that deletes a cloud resource leaves nothing here to restore. On 24 April 2026, a Cursor agent working in PocketOS's staging environment found a Railway API token in an unrelated file and used it to delete the production volume, in about nine seconds. The founder said the backups went with it because Railway kept them in the same volume. Railway says the legacy API path the agent called cascaded the delete, which made the backups look unavailable. Railway's CEO stepped in two days later, and the data was back within the hour. On 1 May, Railway made a volume deleted through its API recoverable for 48 hours, as one deleted in its dashboard already was.
So the first question after a cloud delete is the provider's own undo window, which runs from the moment of the call. If Claude Code made it, the transcript holds the call and its time:
uvx ranwhat watch --days 7 cloud deletes it knows, newest first grep -rl 'volumeDelete' ~/.claude/projects Railway's; use your provider's delete call
ranwhat watch flags cloud deletes such as aws ec2 delete-volume, kubectl delete, terraform destroy and DROP TABLE. A call made straight to a provider's API with curl, as in this case, is not one of them, so search for the provider's own delete call. To narrow what the next one can reach, scope the token.
Stop it happening again.
A hook refuses the command, the sandbox refuses the write, and a commit makes the rest recoverable.
A hook that blocks recursive deletes
Claude Code's hooks guide registers two PreToolUse hooks on Bash. The first appends every command to ~/.claude/bash.log. The second exits 2 when the command contains rm -rf, and exit 2 blocks the call. Both run, so a blocked command is still logged. In .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r .tool_input.command >> ~/.claude/bash.log"
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh"
}
]
}
]
}
}The guide leaves the script to you. This one follows the guide's own blocking example, and widens the match to every short-flag spelling of rm -r and to find -delete. Save it as .claude/hooks/block-rm-rf.sh and run chmod +x on it:
#!/bin/bash INPUT=$(cat) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command') if echo "$COMMAND" | grep -Eq '(^|[^[:alnum:]_])rm +(-[[:alpha:]]+ +)*-[[:alpha:]]*[rR]|find .* -delete'; then echo "Blocked: recursive delete. Ask the user to run it." >&2 exit 2 fi exit 0
It also blocks rm -rf build. That is the trade: run those yourself, or narrow the pattern. Both hooks need jq on your path.
Why not a deny rule
A deny rule matches the command text Claude writes, and Claude Code's permissions page is plain about the limit: Bash(rm *) stops rm -rf build/ but not /bin/rm -rf build/ or bash -c 'rm -rf build/'. A hook sees the whole command, so the script above catches both. It is still text matching: rm --recursive, a command built inside a variable, or a Python script that deletes files all get past it.
The sandbox
The sandbox does not read command text. The operating system enforces it for every Bash, PowerShell or Monitor command and its child processes. By default those commands can write only to the working directory, the per-user temp directory and directories you added, so rm -rf ~/Documents fails and rm -rf src inside the project does not. sandbox.filesystem.denyWrite blocks writes to the paths you name, inside the project too:
{
"sandbox": {
"enabled": true,
"allowUnsandboxedCommands": false,
"filesystem": {
"denyWrite": ["./data", "./migrations"]
}
}
}In a project's .claude/settings.json, ./ paths resolve to the project root. /sandbox opens a panel to turn it on. It runs on macOS, Linux and WSL2, not native Windows. When a command fails under the sandbox, Claude may retry it outside, through the normal permission prompt; allowUnsandboxedCommands: false removes that retry. If the sandbox cannot start, Claude Code warns and runs commands unsandboxed unless sandbox.failIfUnavailable is true.
The write limit holds only on a patched build. Before 2.1.64, a symlink made by a sandboxed command could lead Claude Code's own, unsandboxed process to write outside the workspace without asking (CVE-2026-39861). From 2.1.38 until 2.1.163, a repository could combine a worktree named .git, symlinks and git fsmonitor to overwrite files in your home directory, such as ~/.zshenv (CVE-2026-55607). Before 2.1.247, on macOS, a core.fsmonitor command planted from inside the sandbox ran outside it when Claude Code's own git calls ran, which Accomplish AI called Beltdown. Each needed untrusted content in the session, from a cloned repository or injected instructions. Auto-update has applied all three fixes. If you update by hand, claude --version should print 2.1.247 or later.
Rewind and the sandbox cover each other's gaps. Checkpoints track Claude's file tools and not Bash; the sandbox limits Bash and not Claude's file tools.
Commit before long runs
Checkpoints are not version control, and the documentation says so. A commit before you hand over a long task is a restore point that covers Bash, subagents and your own edits alike. Untracked and ignored files are still only as safe as your backups.
Where each claim comes from.
- Checkpointingwhat rewind tracks, and what it does not
- Explore the .claude directorytranscripts, subagents, file-history, retention
- Settings referencecleanupPeriodDays, sandbox keys
- Configure permissionswhat a Bash rule does not match
- Automate actions with hooksthe two PreToolUse hooks, exit 2
- Configure the sandboxed Bash toolwrite limits, denyWrite, platforms
- GHSA-vp62-r36r-9xqpCVE-2026-39861, symlinks, fixed in 2.1.64
- GHSA-7835-87q9-rgvvCVE-2026-55607, worktrees, fixed in 2.1.163
- Accomplish AI: BeltdownmacOS, fixed in 2.1.247
- The Register: the PocketOS deletion27 April 2026
- Railway: Your AI wants to nuke your databaseblog.railway.com
- Railway changelog: undoable deletes48-hour soft delete, 1 May 2026
- git restoregit-scm.com
- git resetgit-scm.com
- git refloggit-scm.com
- git fsckgit-scm.com
- git cleangit-scm.com
- Git Internals: Git Objectsgit add stores blobs
- ranwhat/watch.pythe deletion and git rules