AI deleted the database: a 7-minute recovery plan
MarkdownWhat to do after data deletion: stop the agent, document the incident, use git reflog, PITR, snapshots, and file recovery. Examples and safeguards.
Read full article
Key takeaways
An AI agent just deleted your database, files, or your entire production environment. The first 7 minutes determine whether you recover your data or lose it forever. This playbook gives you specific commands for each minute. First stop the agent, then document the incident, then recover. No panic—follow the checklist.
Practical information about working with Ailoot is available in the documentation.
What to do in the first minute: STOP, don't panic
Key takeaways
Stop the destructive writer immediately, including any child process that continues after the agent stops. Do not reboot or terminate unrelated processes that may still hold deleted files open. Recoverability is not guaranteed: overwrites and SSD discard/TRIM can make recovery impossible. Preserve evidence and surviving data on a separate verified device before changing the damaged copy.
When this happens to you, the first thing to do is stop the agent. Don't try to undo its actions yourself, don't tell it to “restore everything,” and don't run new prompts. Each subsequent action by the agent could overwrite the disk blocks where your data may still be stored.
Close the agent's terminal
Interrupt the dangerous command immediately with Ctrl+C; do not wait for deletion to finish. Stopping the agent does not necessarily stop its child commands, remote jobs, or database queries. Identify the actual writer and stop or cancel that operation; escalate termination if it continues destroying data. Keep unrelated processes with potentially recoverable open files alive.
Do NOT reboot your laptop
Deleted data may remain recoverable, but storage, database behavior, SSD discard/TRIM, and subsequent writes determine that. Minimize writes to the affected volume; do not assume the bytes still exist.
Do NOT close open processes
Preserve unrelated processes that hold deleted files open, because recovery may be possible through their file descriptors. This does not apply to the destructive writer: stop that writer first. Do not keep a deletion running merely to preserve an open handle.
Start screen recording
Record the exact time, affected paths, and visible command output. If recording or copying logs, save to a separate verified device rather than the damaged volume. Preserve surviving tracked, untracked, ignored files and Git metadata there before attempting recovery.
Next, 6 minutes to recover. Let's go.
Minutes 2–3: document exactly what went wrong
Key takeaways
Diagnose before recovery. Without an exact understanding of “what was deleted, by whom, when, and with which commands,” you'll be recovering blindly and could destroy data that is still intact. Open a new terminal window (not the one where the agent is running), run git status, git log --since, and ps aux | grep claude. Immediately copy the ~/.claude/projects/ folder—it contains the agent's complete session log, which may help with recovery.
Open a new terminal window. Not the one where the agent is running—another one. Run these three commands in sequence.
1. Record exactly what changed in the git repository:
git status
git log --since="30 minutes ago" --all --oneline
A deleted tracked file may still exist in the index or a commit. Preserve the current working tree, including untracked and ignored files, before any restore operation; restoring in place can overwrite surviving edits. Inspect and recover in a separate copy.
2. Check which agent processes are running:
ps aux | grep -i claude
ps aux | grep -i cursor
Identify which process is still performing the dangerous writes; a name match alone is not enough. Stop that writer and verify it has stopped. Preserve unrelated processes with useful open files; if graceful termination fails and destruction continues, force termination may be necessary.
3. Save the agent session log:
Claude Code stores JSONL sessions in ~/.claude/projects/. Cursor stores them in ~/.cursor/. Make a copy right now:
cp -r ~/.claude/projects /external/incident-evidence/claude-projects
Replace /external/incident-evidence/ with an existing evidence directory on a separate verified mounted device. Do not write the backup onto the affected volume or overwrite existing evidence. This folder contains the verbatim conversation with the agent—which commands it ran, what it replied, and in what order. It's your main source for investigating the incident and potentially recovering deleted files from its context (see step 4).
Minute 4: git reflog—your best chance
Key takeaways
Reflog can locate commits that disappeared from branch history, but it does not save uncommitted working-tree files. After preserving surviving data and Git metadata on a separate device, inspect the copied repository and recover a verified commit into a separate worktree. Never begin by resetting the damaged working tree.
git reflog records local reference movements while entries remain retained. Expiration and pruning vary, and unreachable objects can be removed; it is not a guaranteed archive of every past commit. Run the recovery steps below from a preserved repository copy on the separate device.
Step 1. View the log:
git reflog --all
You'll get a list like this:
1a2b3c4 HEAD@{0}: reset: moving to HEAD~3
9z8y7x6 HEAD@{1}: commit: working version of the dashboard
5w4v3u2 HEAD@{2}: commit: added admin panel
Look for the point before the agent caused the damage. Each entry is a “snapshot” of the repository state at that moment.
Step 2. Recover into a separate working tree:
After copying the repository and all surviving work to a separate device, run this from that preserved repository copy. Replace the value with the exact commit SHA you checked in reflog, and choose an unused branch and destination:
RECOVERY_COMMIT='replace-with-verified-commit-sha'
git worktree add -b recovery-check ../recovery-check "$RECOVERY_COMMIT"
Inspect the recovered files in ../recovery-check and compare them with the preserved uncommitted files before copying anything back. The command checks out the selected commit separately; it does not reset the damaged working tree. Linked worktrees share the copied repository’s Git object store, so this is a recovery workspace, not an independent backup. See the official git-worktree documentation.
If reflog was also cleaned (the agent managed to run git gc):
git fsck --lost-found
This command finds “lost” commits and blobs (file objects) that are no longer attached to any branch. They'll appear in .git/lost-found/commit/ and .git/lost-found/other/. To restore a file from there:
git cat-file -p <SHA1-blob> > recovered-file.ext
When git reflog does NOT help:
- The file was only in the working tree, without a commit or stash → reflog can't see it
- More than 90 days have passed (the standard
gc.reflogExpire) andgit gcwas run - The entire repository was deleted (
rm -rf .git)—then you need Time Machine or a snapshot
Minute 5: restore the database from a backup
Key takeaways
PostgreSQL Point-In-Time Recovery (PITR) restores a database to any second within a 7–35 day window. It works with Yandex Cloud Managed Postgres, AWS RDS, Supabase Pro, and through a local pg_dump cron job in Coolify. The key requirement is that PITR must have been enabled before the incident; you can't enable it retroactively. Always restore to a new instance, never on top of a live database, so the rollback doesn't destroy even more data. If PITR wasn't enabled, use the latest snapshot.
The database was deleted with DROP DATABASE, DROP TABLE, or DELETE FROM ... WHERE ... (without a WHERE clause). What should you do?
Step 1. Find out whether your provider offers PITR:
| Provider | PITR window | Restore command |
|---|---|---|
| AWS RDS | 1–35 days (configurable) | aws rds restore-db-instance-to-point-in-time --restore-time 2026-05-28T14:30:00Z |
| Yandex Cloud Managed Postgres | 7 days (by default) | Restore from backup in the console + specify the time |
| Supabase Pro | 7 days (Pro+ only) | UI: Database → Backups → Restore |
| Coolify + local pg_dump | According to cron schedule | pg_restore -d new_db backup.dump |
Step 2. Restore to a new instance, not the old one:
Never restore over a live database. Create a new instance from the backup, then switch the application to it. If something goes wrong, you can switch DNS back.
Step 3. If PITR wasn't enabled, use the latest snapshot:
# AWS RDS, list snapshots:
aws rds describe-db-snapshots --db-instance-identifier my-db
# Restore from a snapshot:
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier my-db-restored \
--db-snapshot-identifier rds:my-db-2026-05-28-12-00
Supabase caveat, which caught many people in 2026: when PITR is enabled, daily backups stop being created. Counterintuitive behavior; you could end up with no backups at all. Check Dashboard → Database → Backups to make sure PITR is active AND that you have a snapshot to restore from.
Minute 6: Time Machine, lsof, and disk snapshots
Key takeaways
If files were deleted from disk—not from git or a database—there are three practical recovery methods. On macOS, use Time Machine with hourly snapshots (if the laptop was connected to a Time Capsule or external drive). On Linux, run sudo lsof | grep deleted—it can retrieve files that a process still has open (95% chance); copy them through /proc/PID/fd/N. For critical cases, create a snapshot of the whole partition with dd, then recover with extundelete or photorec.
macOS—Time Machine:
# List snapshots:
tmutil listbackups
# Restore a file from the latest backup:
tmutil restore /Volumes/Backup/.../my-project/file.tsx ~/projects/my-project/file.tsx
Time Machine makes snapshots every hour, keeps hourly snapshots for a day and daily snapshots for a week, and weekly snapshots indefinitely. If your laptop is connected to a Time Capsule or external drive, your project is there with hourly granularity.
Linux—the file was deleted, but a process still has it open:
# Find all “deleted” files that a process still has open:
sudo lsof | grep deleted
# You'll get lines like:
# node 12345 user 24w REG 8,1 4096 1234567 /path/to/deleted/file.txt (deleted)
# Copy the file directly from /proc/PID/fd/N:
sudo cp /proc/12345/fd/24 ~/recovered-file.txt
The chance of success is ~95% if the process wasn't closed. Works on ext4, xfs, btrfs.
macOS—equivalent using lsof:
lsof | grep deleted
Copying the file is harder (there's no /proc/), but you can do it with lldb:
lldb -p 12345
(lldb) memory read --outfile recovered.txt --binary 0xADDRESS 0xSIZE
Snapshot of the entire disk (Linux, last resort):
sudo dd if=/dev/sda1 of=/external/disk-image.img bs=4M status=progress
This saves the entire partition as an image. Then recover using extundelete or photorec. The chance of recovery depends on how much has been written to the disk since deletion. If nothing has been written, it's 80–90%. If it has already been running for an hour, it's 20–30%.
Minute 7: what if there is no backup
Key takeaways
No backup, empty git reflog, and the disk has already been overwritten with new data. Two last-resort options remain: contact your provider's support (AWS, Yandex Cloud, and Google Cloud periodically recover data from their internal storage) or restore through S3 versioning (if it was enabled, all versions are physically there).
Option 1—provider support:
AWS, Yandex Cloud, Google Cloud, and DigitalOcean periodically recover data from their internal storage, even after a customer disaster, especially on paid plans. Open a support ticket, give the exact time of the incident, and include screenshots of the state. This most often works with major providers that keep automatic backups on their end. The chance is 40–60%; the response window is 24–72 hours.
Option 2—S3 versioning:
If files were in S3 (or Yandex Object Storage) and versioning was enabled, all versions physically exist; they're simply marked as “deleted.”
# AWS—list versions:
aws s3api list-object-versions --bucket my-bucket --prefix files/
# Restore a version:
aws s3api copy-object \
--bucket my-bucket \
--copy-source my-bucket/files/data.json?versionId=ABC123 \
--key files/data.json
Critical caveat: Claude Code doesn't keep sessions forever—old JSONL files are deleted automatically after several weeks. To ensure you have a month of JSONL history in case of a disaster, copy ~/.claude/projects/ to an external drive once a week with a simple cron job:
# In crontab, every Sunday at 03:00:
0 3 * * 0 rsync -av ~/.claude/projects/ /external/claude-history-backup/
It's free and saves you when “everything is gone, there's no backup, and reflog is empty.”
7 rules to make sure this never happens again
Key takeaways
Seven rules that keep an AI agent from deleting your database or code. Use Plan Mode before destructive operations. Use tested hooks to detect selected dangerous command strings, alongside enforced permission limits. Give the agent a read-only database user. Auto-commit after every file edit. Use an isolated sandbox folder for experiments. Take disk snapshots with restic every hour. Archive JSONL sessions to an external drive.
1. Plan Mode before any destructive operation. In Claude Code:
claude --plan
The agent builds a plan before acting. You read it, see DROP TABLE users, and cancel. Without Plan Mode, the agent decides for itself and executes in milliseconds. Learn more in the Plan Mode in Claude Code guide.
2. Hooks as an extra tripwire. Use a tested PreToolUse hook that reads the event JSON from stdin and exits 2 for a detected high-risk action. The complete example below covers Bash, Write, and Edit; it is not a security boundary and can miss indirect operations. Keep database privileges and the sandbox restrictive.
3. Read-only DB user for the agent. Create a separate user for the agent that does not have DROP, TRUNCATE, or DELETE permissions:
CREATE USER claude_readonly WITH PASSWORD 'xxx';
GRANT CONNECT ON DATABASE myapp TO claude_readonly;
GRANT USAGE ON SCHEMA public TO claude_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO claude_readonly;
When the agent really needs to write, switch to a different user manually. A read-only user is sufficient for everyday tasks (data analysis, reports, dashboards).
4. Keep reviewed changes in Git. A PostToolUse hook in .claude/settings.json can print a reminder after Write and Edit:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "printf '%s\\n' 'Review the diff and manually commit only the agreed files.' >&2"
}
]
}
]
}
}
This example only prints a reminder; it does not stage files or create commits. Review the diff, then manually commit only the agreed files. The reflog does not save uncommitted changes.
5. Sandbox project for experiments. Never let the agent work in a project that contains real customer data. Create a separate ~/sandbox-projects/ folder, with a copy of the project and synthetic data. Use it to try a new prompt, agent, or MCP server. If it breaks something, no harm done.
6. Disk snapshots with restic every hour.
# Install:
brew install restic # macOS
sudo apt install restic # Linux
# Create repository:
restic init --repo /external/backups
# Back up the current project:
restic --repo /external/backups backup ~/projects
# Schedule cron every hour:
crontab -e
# Add:
0 * * * * restic --repo /external/backups backup ~/projects
Seven days of hourly backups, four weeks of daily backups—and you can always roll back to any point in the past. It's free and takes 10 minutes to set up.
7. Archive Claude Code JSONL sessions. Already mentioned above, but repeating as a separate rule: schedule a weekly rsync of ~/.claude/projects/ to an external drive. It's free and takes one line in crontab.
Claude Code hooks to block dangerous commands
Key takeaways
The example below is a PreToolUse tripwire for selected Bash command strings and sensitive Write/Edit paths. It reads stdin JSON and returns exit 2 for matches. It can miss indirect or obfuscated actions, so it supplements a sandbox and restricted credentials rather than replacing them. Merge the hook into the existing configuration and test its behavior before use.
Merge this entry into .claude/settings.json without replacing existing hooks:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/check-risk.py\""
}
]
}
]
}
}
Create .claude/hooks/check-risk.py with the following Python 3 script:
import json
import re
import sys
from pathlib import Path
try:
event = json.load(sys.stdin)
tool = event["tool_name"]
data = event["tool_input"]
blocked = False
if tool == "Bash":
command = data["command"]
blocked = bool(re.search(
r"drop\s+(database|table)|truncate\s+table|"
r"delete\s+from.*where\s+1|rm\s+-rf|"
r"git\s+reset\s+--hard|git\s+push.*(--force|-f)|"
r"chmod\s+-R\s+777",
command, re.IGNORECASE | re.DOTALL))
elif tool in ("Write", "Edit"):
path = Path(data["file_path"]).resolve()
blocked = (".git" in path.parts or path.name.startswith(
(".env", "credentials", "secrets")))
except (ValueError, KeyError, TypeError, OSError):
print("BLOCKED: invalid hook input", file=sys.stderr)
sys.exit(2)
if blocked:
print("BLOCKED: high-risk command or sensitive path", file=sys.stderr)
sys.exit(2)
sys.exit(0)
The hook reads the documented JSON event from stdin, checks tool_input.command or tool_input.file_path, and exits 2 on a match. It checks both Write and Edit. Test a harmless allowed fixture and a blocked fixture before relying on it, and verify that Python and the configured script actually start.
This is a lexical tripwire: it can miss reordered flags, scripts, aliases, obfuscated commands, other tools, and indirect writes; it can also reject harmless quoted examples. It does not grant or remove OS/database permissions. Keep production credentials outside the agent environment, use a read-only database role and an enforced sandbox, and protect the hook configuration from modification. See the official hook input and exit-code reference.
For an intentional exceptional operation, review it and run it outside the agent with the appropriate restricted account. A shell environment variable is not a documented hook-disable interface, and hooks do not govern ordinary terminal commands.
When to give AI permissions (and when not to)
Key takeaways
Categorically do NOT give an agent the --dangerously-skip-permissions flag for projects with real data. In this YOLO mode, all protections are disabled, the agent runs any Bash command and writes files without confirmation, and spawned subagents inherit YOLO with no way to override it. Use YOLO only in a sandbox folder for experiments with new MCP servers or prompts on dummy data. For everything else, use precision permissions through .claude/settings.json with explicit allow and deny rules.
Anthropic documents the --dangerously-skip-permissions flag (also known as YOLO mode) as an option for “trusted environments.” In the reality of 2026, it's a direct path to disaster. When an agent doesn't have to ask, it:
- Runs any Bash command without confirmation
- Writes files without asking
- Can invoke a subagent, which inherits YOLO with no way to undo it
The third point is the most insidious. If you start the main agent with YOLO, its subagents are YOLO too, and you can't override it. One prompt—and a cascade of 10 parallel agents can do anything to your system.
Rule:
| Situation | Is YOLO acceptable? |
|---|---|
| Experimenting with a new MCP server in a sandbox folder | ✅ Yes |
| Testing a new prompt on dummy data | ✅ Yes |
| Working with real project files | ❌ No |
| Any command that touches a database | ❌ Never |
| Deploying to production | ❌ Never |
| Managing infrastructure (AWS, Yandex Cloud) | ❌ Never |
Alternative—precision permissions. In .claude/settings.json:
{
"permissions": {
"allow": [
"Bash(git status:*)",
"Bash(git log:*)",
"Bash(npm run build:*)",
"Read(*)",
"Edit(src/**)"
],
"deny": [
"Bash(rm:*)",
"Bash(git push:*)",
"Edit(.env)",
"Edit(prisma/migrations/*)"
]
}
}
The agent sees what is and isn't allowed. You don't have to click “yes” or “no” every time—the agent works quickly, and you control the perimeter.
Next: a 7-minute checklist to bookmark
Key takeaways
Bookmark this guide right now. When disaster strikes, open the page and follow the 10-point checklist in order. It will save you 30 minutes of panic and keep you from accidentally overwriting data that may still be recoverable. After you recover, come back and configure the 7 preventive rules so disaster doesn't strike again next week.
Checklist: “AI deleted it—what do I do?”
- Stop the actual destructive writer immediately; verify child or remote operations have stopped
- Do not reboot; preserve unrelated processes holding deleted files open
- Record incident time and command evidence on a separate verified device
- Copy surviving files, Git metadata, and relevant session logs to that separate device
- In the preserved repository copy, inspect
git reflog --alland recover a verified commit into a separate worktree - If git didn't help—
git fsck --lost-found - Database: PITR (AWS/Yandex/Supabase) or latest snapshot
- Files: Time Machine on Mac, lsof on Linux
- Last resort: provider support + S3 versioning
- After recovery, configure the 7 preventive rules in this guide
Next—systematic protection:
- Plan Mode in Claude Code—so the agent makes a plan before a destructive operation
- Hooks in Claude Code—ready-made templates for blocking dangerous commands
- How to roll back changes in Claude Code—a detailed guide to
/rewind, git, and 5 standard rollback methods - Security review in Claude Code—regular code security checks
Sources
More details
Questions about AI deleted the database: a 7-minute recovery plan
Should I wait for the agent’s current command to finish?
No. Stop the destructive writer immediately and verify that its child, remote, or database operation has stopped. Preserve unrelated processes that may hold recoverable deleted files; stopping the agent alone may leave another writer running.
Can I start recovery with git reset --hard?
First preserve surviving tracked, untracked, and ignored files plus Git metadata on a separate device. Inspect the copied repository and recover a verified commit into a separate worktree; an in-place hard reset can destroy surviving uncommitted edits.