@mohitagw15856/undo
v0.1.0
Published
You just ran something you should not have. Paste it here, get the recovery steps. Works offline.
Downloads
101
Maintainers
Readme
You just ran something you should not have. Start here.
A recovery registry for destructive commands. Every entry answers the same four questions in the same order, because at 2am you do not want an essay: is it recoverable, how long have I got, what do I type, and how do I stop doing this.
npx github:mohitagw15856/undo "git reset --hard" # paste what you ran, get the stepsRuns straight from this repo — nothing to install, nothing to clean up. (The bare name undo on npm belongs to an unrelated package, so the published name here is @mohitagw15856/undo.)
Before anything else: stop writing to the disk. Most recovery failures are not because the data was unrecoverable — they are because something overwrote it during the twenty minutes spent panicking. Close the editor, stop the build, and read the entry before you type.
Everything, worst first
| | Command | Recoverable? | You have | What it costs you |
| :-: | --- | :-: | --- | --- |
| 🔴 | committed a secret | No | — | The secret is in git history, in every clone, and if pushed to a public repo it is being scraped within minutes. |
| 🔴 | git clean -fd | No | — | Deletes untracked files and directories. With -x, ignored files too, which includes .env. |
| 🟡 | > file | Sometimes | — | The shell truncates the target to zero bytes before the command runs — so a typo destroys the file even if the command then fails. |
| 🟡 | answered Yes to a destructive prompt | Sometimes | — | Varies. What matters is that you now need to know what it actually did. |
| 🟡 | aws ec2 terminate-instances | Sometimes | — | The instance is destroyed. Root EBS volumes with DeleteOnTermination=true go with it; other attached volumes usually survive. |
| 🟡 | aws s3 rm --recursive | Sometimes | — | Deletes objects. With versioning on, it writes delete markers instead of destroying data. |
| 🟡 | chmod -R 777 | Sometimes | — | Overwrites every permission bit in the tree. The previous permissions are not recorded anywhere. |
| 🟡 | crontab -r | Sometimes | — | Deletes your entire crontab. There is no confirmation prompt, and -r sits directly next to -e on the keyboard. |
| 🟡 | dd of=/dev/disk | Sometimes | — | Writes raw bytes over the partition table and filesystem of whatever device you named. |
| 🟡 | DELETE FROM table | Sometimes | — | Every row is affected. TRUNCATE also resets sequences and cannot be rolled back in MySQL. |
| 🟡 | docker system prune -a --volumes | Sometimes | — | Removes stopped containers, unused images, networks and — with --volumes — named volumes and everything in them. |
| 🟡 | DROP TABLE | Sometimes | — | The table and its data are removed. In PostgreSQL and MySQL this is not soft-deleted anywhere. |
| 🟡 | emptied the Trash | Sometimes | — | The files are unlinked. macOS does not keep a second copy. |
| 🟡 | gh repo delete | Sometimes | 90 days | The repository, its issues, PRs, releases, wiki and Actions history are deleted. |
| 🟡 | git checkout -- file | Sometimes | — | Overwrites the file in the working tree with the version from the index. Uncommitted edits are discarded. |
| 🟡 | git push --force | Sometimes | — | Replaces the remote branch history. Commits other people had pulled are orphaned on the server. |
| 🟡 | git reset --hard | Sometimes | 90 days | Moves the branch pointer and overwrites the working tree and index to match. |
| 🟡 | history -c | Sometimes | — | Clears the in-memory history list. On exit the shell may then write that empty list over the history file. |
| 🟡 | kill -9 | Sometimes | — | SIGKILL cannot be caught, so the process gets no chance to flush buffers, write state or clean up locks. |
| 🟡 | kubectl delete namespace | Sometimes | — | Cascades to every resource in the namespace, including PersistentVolumeClaims — which may delete the underlying disk depending on the reclaim policy. |
| 🟡 | npm publish | Sometimes | 72 hours | Publishes to the public registry, permanently. Secrets in the tarball are now public and must be treated as compromised. |
| 🟡 | rm -rf | Sometimes | — | Unlinks files immediately. There is no trash, no confirmation, and no undelete on APFS, ext4 or NTFS via this path. |
| 🟡 | terraform destroy | Sometimes | — | Deletes every resource in the state file, in dependency order, in the real cloud account. |
| 🟢 | force quit with unsaved work | Yes | — | The process dies without running its save-on-exit path. |
| 🟢 | git branch -D | Yes | 30 days | Deletes the branch label even if it is unmerged. The commits become unreachable. |
| 🟢 | git commit --amend | Yes | 90 days | Replaces the previous commit with a new one. The original is orphaned. |
| 🟢 | git rebase on a shared branch | Yes | 90 days | Every commit gets a new SHA. Anyone who had the old commits now has a diverged history, and merging the two duplicates every commit. |
| 🟢 | git stash drop | Yes | 30 days | Removes the stash entry. git stash clear removes all of them at once with no confirmation. |
You have is the window before recovery stops working — a git reflog expiry, an unpublish deadline, a soft-delete retention period. — means there is no clock, which is either very good news or very bad news depending on the row.
Git
committed a secret
🔴 No — Gone. Recovery means a backup or nothing.
What it does: The secret is in git history, in every clone, and if pushed to a public repo it is being scraped within minutes.
Also: pushed .env · committed a private key · hardcoded API key
Recovery
Rotate the secret first. Everything else is secondary and slower than the scrapers. Public GitHub is scanned continuously; assume the key is used.
Revoke and reissue the credential at its source.
Then clean the history, if it is worth doing:
# modern, fast, and what the git docs now recommend git filter-repo --invert-paths --path .env # or, for a value rather than a file git filter-repo --replace-text <(echo 'sk-1234==>REDACTED')Force-push all branches and tags, and tell every collaborator to re-clone — their old clones still contain it.
On GitHub, old commits stay reachable by SHA even after a force push. Ask support to purge the cached views, or accept that it is exposed.
Next time
git-secrets or gitleaks as a pre-commit hook. Add .env to a global
gitignore so it is never repo-specific. Use the platform's secret storage
rather than files, and enable push protection on the repository.
git branch -D
🟢 Yes — Reliably recoverable if you act. You have about 30 days.
What it does: Deletes the branch label even if it is unmerged. The commits become unreachable.
Also: git branch --delete --force
Recovery
git reflog # the branch tip is in here git checkout -b
he reflog entry is gone:
git fsck --unreachable --no-reflogs | grep commit git log --oneline # identify the right one git branch
achable objects are pruned by git gc after 30 days, or immediately if
one ran git gc --prune=now.
Next time
Use git branch -d (lowercase), which refuses to delete unmerged work.
git checkout -- file
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Overwrites the file in the working tree with the version from the index. Uncommitted edits are discarded.
Also: git restore file · git checkout .
Recovery
If the change was ever staged with git add, the blob is in the object
database and recoverable:
git fsck --lost-found
ls .git/lost-found/other/
# or, if you know roughly when:
git stash list && git log -g --allIf it was never staged, git never saw it. Fall back to your editor's local
history — VS Code Timeline, JetBrains Local History — which is the thing
that actually saves people here.
Next time
git add early and often, even on work you are not ready to commit. Staging
is free and makes everything recoverable. git stash before experiments.
git clean -fd
🔴 No — Gone. Recovery means a backup or nothing.
What it does: Deletes untracked files and directories. With -x, ignored files too, which includes .env.
Also: git clean -fdx · git clean -xdf
Recovery
Git never knew about these files, so git cannot help. In order of hope:
- Editor local history — the only thing that regularly saves people:
- VS Code:
Timelineview in the Explorer, per file - JetBrains: right-click the folder →
Local History→Show History
- VS Code:
- Time Machine / File History / Dropbox / iCloud version history, if the directory was covered.
.envspecifically: check.env.example, your password manager, the deployment platform's environment variable page, or a teammate.
If none of those apply, it is gone. This is one of the few genuinely unrecoverable git commands.
Next time
git clean -nd first — -n is a dry run and lists exactly what would go.
Make it muscle memory: never type -f before you have typed -n.
git commit --amend
🟢 Yes — Reliably recoverable if you act. You have about 90 days.
What it does: Replaces the previous commit with a new one. The original is orphaned.
Also: git rebase -i · git rebase --squash
Recovery
git reflog git reset --hard HEAD@{1} # the commit before the amend
answer for a rebase that went wrong. ORIG_HEAD is set automatically by
se and merge, so this usually works too:
git reset --hard ORIG_HEAD
Next time
Tag before a risky rebase — git tag pre-rebase costs nothing and reads clearly in a week.
git push --force
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Replaces the remote branch history. Commits other people had pulled are orphaned on the server.
Also: git push -f · git push --force-with-lease
Recovery
Do not let anyone else push. Every second the old history stays reachable somewhere is a second you can still recover it.
Anyone who has not pulled yet is your backup. Ask the team. Someone's laptop almost certainly still has the old commits:
git reflog # on their machine git push origin <sha>:refs/heads/rescueOn GitHub, force-pushed commits stay reachable by SHA for a while even though they are on no branch. If you know the SHA — it is in the push event payload, the PR timeline, or any CI run log:
https://github.com/OWNER/REPO/commit/<sha> git fetch origin <sha>GitHub's Events API still lists the old head:
gh api repos/OWNER/REPO/events --jq '.[] | select(.type=="PushEvent") | .payload.before'
Next time
Use --force-with-lease always. It refuses when the remote moved since your
last fetch, which is exactly the case where force-pushing hurts someone. Then
protect the branch so the question stops arising.
git rebase on a shared branch
🟢 Yes — Reliably recoverable if you act. You have about 90 days.
What it does: Every commit gets a new SHA. Anyone who had the old commits now has a diverged history, and merging the two duplicates every commit.
Also: git pull --rebase on main · rebasing after others pulled
Recovery
If nobody has pulled the rebased version yet, put it back:
git reflog
git reset --hard ORIG_HEAD # rebase sets this automatically
git push --force-with-leaseIf someone already pulled and merged, you now have duplicated commits. The fix is per-person, not central:
git fetch origin
git reset --hard origin/<branch> # discard local, take the remote truthAnyone with unpushed work should git cherry-pick it onto the new history
rather than merging.
Next time
Only rebase branches nobody else has. The rule "rebase local, merge shared" exists precisely because this recovery is unpleasant for everyone at once.
git reset --hard
🟡 Sometimes — Depends on what was configured beforehand. You have about 90 days.
What it does: Moves the branch pointer and overwrites the working tree and index to match.
Also: git reset --hard HEAD · git reset --hard origin/main
Recovery
Committed work is safe — the commits still exist, only the branch label moved.
git reflog # find the SHA from before the reset
git reset --hard <sha> # or: git branch rescue <sha>Uncommitted changes are a different story. If they were never staged, they
are gone. If they were EVER staged (git add), the blobs are still in the
object database:
git fsck --lost-found
ls .git/lost-found/other/ # your blobs, unnamed, one file eachReflog entries expire after 90 days by default (30 for unreachable ones), so this window is real but generous.
Next time
git stash before any reset you are unsure about — it costs two seconds and
makes the operation reversible. git reset --keep refuses to run instead of
destroying uncommitted work.
git stash drop
🟢 Yes — Reliably recoverable if you act. You have about 30 days.
What it does: Removes the stash entry. git stash clear removes all of them at once with no confirmation.
Also: git stash clear · git stash pop with conflict
Recovery
Stashes are commits, so they survive as unreachable objects:
git fsck --unreachable | grep commit
git show <sha> # find the right one
git stash apply <sha>Faster if you only just did it — the SHA is printed in the output of
git stash drop itself. Scroll up before you close the terminal.
Next time
Prefer git stash apply over git stash pop. apply keeps the stash entry
after applying it; pop deletes it, including when the apply half-fails on a
conflict and leaves you in a mess.
Shell & filesystem
> file
🟡 Sometimes — Depends on what was configured beforehand.
What it does: The shell truncates the target to zero bytes before the command runs — so a typo destroys the file even if the command then fails.
Also: echo > file · : > file · command > existing-file
Recovery
If any process still holds the file open, the data is still on disk under the old inode. On Linux this is a genuine rescue:
lsof <file>
cp /proc/<pid>/fd/<n> <file>.recoveredA tailing log, an editor, or the server itself will often be that process. Once every handle closes, the data is unlinked and gone.
Otherwise: backups, version control, editor local history.
Next time
set -o noclobber makes > refuse to overwrite an existing file (use >|
when you mean it). Prefer >> when appending is what you actually want.
chmod -R 777
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Overwrites every permission bit in the tree. The previous permissions are not recorded anywhere.
Also: chmod -R · chown -R · chmod 777 /
Recovery
Nothing stores the old modes, so you rebuild them:
- In a git repo: git tracks only the executable bit, but that is usually
the one that matters —
git diffwill show mode changes, andgit checkout .restores them. - System directories on macOS: Disk Utility → First Aid repairs permissions on system paths only.
- Linux packages: reinstall to restore package-owned modes —
dpkg --verifyorrpm -Valists exactly what changed first. chown -Ron/: this typically requires a reinstall or a restore. Boot from external media and copy user data off first.
A sane default for a project tree: find . -type d -exec chmod 755 {} + and
find . -type f -exec chmod 644 {} +, then fix executables individually.
Next time
Capture the state first — getfacl -R . > perms.txt (restore with
setfacl --restore=perms.txt). And 777 is essentially never the right
answer; it is a way of not diagnosing a permissions problem.
crontab -r
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Deletes your entire crontab. There is no confirmation prompt, and -r sits directly next to -e on the keyboard.
Also: crontab -ri
Recovery
Backups of the spool file — this is where it usually is:
/var/spool/cron/crontabs/<user> # Debian/Ubuntu /var/spool/cron/<user> # RHEL/CentOS /usr/lib/cron/tabs/<user> # macOSRestore that path from Time Machine,
restic, or a filesystem snapshot.Mail: cron emails output to the user. Old mail in
/var/mail/<user>often reveals what was running and how often.Syslog:
grep CRON /var/log/syslog*lists every command cron ran, with times. You can reconstruct the schedule from the pattern.
Next time
alias crontab='crontab -i' prompts before destroying. Better: keep the
crontab in version control and install it with
crontab crontab.txt, so the file on disk is never the only copy.
dd of=/dev/disk
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Writes raw bytes over the partition table and filesystem of whatever device you named.
Also: dd if=image.iso of=/dev/sda · Etcher to the wrong drive
Recovery
Unmount the disk and stop immediately. Do not let anything else write.
If only the partition table was overwritten — likely, if you stopped early — the filesystems are still there and can be found:
testdisk /dev/sdX # scans for lost partitions, rewrites the tableFilesystem-level carving for individual files:
photorec. It ignores the partition table entirely and recovers by file signature. Write the output to a different disk.Overwritten regions are gone. dd is a byte-for-byte write; there is no journal and no shadow copy.
Next time
Say the device name out loud before pressing enter. lsblk immediately
before, every time. Prefer tooling that refuses system disks — and note that
/dev/disk2 on macOS is not the same disk after a reboot.
history -c
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Clears the in-memory history list. On exit the shell may then write that empty list over the history file.
Also: rm ~/.bash_history · rm ~/.zsh_history
Recovery
Do not close this terminal. Recovery depends on it staying open.
The on-disk file is usually still intact until the shell exits and overwrites it. Copy it now, from a different terminal:
cp ~/.zsh_history ~/zsh_history.backupThen stop the current shell from writing over it:
unset HISTFILE # in the affected shell, before exitingIf the file is already gone: Time Machine, or
lsof | grep historyin case a process still holds a handle to it.
Next time
setopt INC_APPEND_HISTORY in zsh (or PROMPT_COMMAND='history -a' in bash)
writes each command immediately instead of at exit, which makes the file
authoritative and this whole failure mode disappear.
kill -9
🟡 Sometimes — Depends on what was configured beforehand.
What it does: SIGKILL cannot be caught, so the process gets no chance to flush buffers, write state or clean up locks.
Also: killall -9 · pkill -9 · Force Quit
Recovery
The process is gone; the question is what it left behind.
- A database killed mid-write usually recovers on next start via its write-ahead log. Start it and read the logs before doing anything clever.
- A stale lock file is the common aftermath —
postmaster.pid,.lock,LOCK. Remove it only after confirming withpsthat no process is actually running. - Unflushed application buffers are lost. Anything that had not hit disk was never written.
- Half-written files: check size and tail before trusting them.
Next time
Try kill (SIGTERM) and wait ten seconds first — well-behaved processes shut
down cleanly. kill -9 should be the second attempt, never the first.
rm -rf
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Unlinks files immediately. There is no trash, no confirmation, and no undelete on APFS, ext4 or NTFS via this path.
Also: rm -r · rm -f · unlink
Recovery
Stop writing to that disk now. Every write reduces your chances. If it is the system disk, that means stop working on the machine.
In order of likelihood:
A process still has the file open — this is the surprise recovery and it works more often than people expect. On Linux:
lsof | grep deleted cp /proc/<pid>/fd/<n> /tmp/recoveredBackups: Time Machine (
tmutil),restic/borg, Dropbox or iCloud version history, your editor's local history, a Docker volume, anode_modulescopy, a CI artifact.Git: if it was ever committed,
git checkout .brings it back.Undelete tools — only worth trying on a spinning disk or a non-TRIM drive. On modern SSDs, TRIM has usually already zeroed the blocks:
photorec,testdisk,extundelete.
Next time
Install trash-cli and alias rm to it, so deletes are reversible by
default. Never build a path by variable interpolation without a guard —
rm -rf "$DIR/" deletes / when $DIR is unset. set -u in every script.
Databases
DELETE FROM table
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Every row is affected. TRUNCATE also resets sequences and cannot be rolled back in MySQL.
Also: UPDATE without WHERE · TRUNCATE TABLE
Recovery
Have you committed? If not:
ROLLBACK;This is the single most common successful recovery, and the reason to run
BEGIN;before anything destructive.Committed, PostgreSQL: point-in-time recovery from WAL. Deleted rows technically remain in the heap until vacuumed, but there is no supported way to read them — do not pin your hopes on
pg_dirtyread.Committed, MySQL with binlog: replay to just before the statement.
mysqlbinlog --stop-datetime="2026-08-15 14:31:59" binlog.000123 | mysqlManaged databases (RDS, Cloud SQL, PlanetScale, Neon): console → restore to point in time. Neon branches make this close to instant.
Next time
Write the WHERE clause before the DELETE. Run it as a SELECT first and
read the row count. Use a client with a "safe updates" mode —
SET sql_safe_updates = 1 in MySQL rejects an unqualified DELETE outright.
DROP TABLE
🟡 Sometimes — Depends on what was configured beforehand.
What it does: The table and its data are removed. In PostgreSQL and MySQL this is not soft-deleted anywhere.
Also: DROP DATABASE · DROP SCHEMA
Recovery
If you are still inside a transaction, stop and think, then:
ROLLBACK;PostgreSQL has transactional DDL, so an uncommitted
DROP TABLErolls back completely. MySQL does not — DDL commits implicitly there.Point-in-time recovery, if WAL/binlog archiving is on. This is the real answer for production:
# Postgres: restore base backup, replay WAL to just before the drop recovery_target_time = '2026-08-15 14:32:00' # RDS / Cloud SQL / Aurora: console → Restore to point in timePITR restores to a new instance. You then copy the one table back.
The most recent logical dump —
pg_restore -t <table>can pull a single table out of a full dump without touching anything else.
Next time
Revoke DDL from the application role. Require a transaction and an explicit
count first: BEGIN; SELECT count(*) FROM t; — if the number surprises you,
you are on the wrong database. Set \set AUTOCOMMIT off in .psqlrc.
Docker
docker system prune -a --volumes
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Removes stopped containers, unused images, networks and — with --volumes — named volumes and everything in them.
Also: docker volume rm · docker volume prune · docker rmi
Recovery
- Images: re-pull them. Annoying, not fatal, unless the tag was rebuilt or deleted upstream. Check whether the digest still exists.
- Containers: recreated from your compose file. Anything written inside a container and not in a volume was already ephemeral.
- Named volumes: this is the real loss. Databases live here. On Linux the
data was under
/var/lib/docker/volumes/<name>/_dataand is now unlinked — filesystem-level undelete is the only route, and on an SSD with TRIM it is almost certainly gone. - Docker Desktop: everything lives in one big VM disk image, so per-file recovery is not realistic.
Restore from your database backup. If you do not have one, this is the moment you find out.
Next time
docker system prune without -a --volumes is much safer and does what
people usually mean. Bind-mount anything you care about to a host path that
your normal backups already cover, instead of trusting a named volume.
Kubernetes
kubectl delete namespace
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Cascades to every resource in the namespace, including PersistentVolumeClaims — which may delete the underlying disk depending on the reclaim policy.
Also: kubectl delete pvc · kubectl delete -f · helm uninstall
Recovery
Check the PV reclaim policy immediately. If it was
Retain, the underlying disk still exists and is merely Released:kubectl get pv | grep Released # edit the PV, clear .spec.claimRef, then re-create the PVC to bind itIf it was
Delete, the cloud disk is being deleted right now. Go to the cloud console and look for a snapshot.Velero or similar:
velero restore create --from-backup <name>.etcd snapshot: restores cluster state, but not data on deleted disks.
GitOps: if the manifests are in git, re-applying restores the configuration in minutes. It does not restore data.
Next time
Set persistentVolumeReclaimPolicy: Retain on anything stateful. Never point
kubectl at a cluster without checking kubectl config current-context
first — put it in your shell prompt. Use kubectl delete --dry-run=server.
AWS
aws ec2 terminate-instances
🟡 Sometimes — Depends on what was configured beforehand.
What it does: The instance is destroyed. Root EBS volumes with DeleteOnTermination=true go with it; other attached volumes usually survive.
Also: terraform destroy · gcloud compute instances delete
Recovery
Check for snapshots first — this is the whole game:
aws ec2 describe-snapshots --owner-ids self \ --filters Name=volume-id,Values=vol-xxxxA snapshot means you can create a new volume and attach it.
Non-root volumes with
DeleteOnTermination=falseare still there,available, waiting to be attached to a new instance.AWS Backup / Data Lifecycle Manager recovery points.
The instance itself cannot be un-terminated. You rebuild it — which is fine if it was in Terraform or an AMI, and painful if it was a pet.
Next time
Enable termination protection on anything long-lived. Set
DeleteOnTermination=false on data volumes. Above all: if you cannot
recreate an instance from code, the instance is a liability regardless of
whether anyone deletes it.
aws s3 rm --recursive
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Deletes objects. With versioning on, it writes delete markers instead of destroying data.
Also: aws s3 rb --force · s3 delete-object
Recovery
If versioning was enabled, you have lost nothing. The objects are hidden behind delete markers:
aws s3api list-object-versions --bucket B --prefix P \
--query 'DeleteMarkers[].{Key:Key,Id:VersionId}' --output json > markers.json
# delete the delete markers to reveal the objects again
aws s3api delete-object --bucket B --key K --version-id <marker-id>If versioning was off, the objects are gone. Check for:
- A replication target bucket in another region
- AWS Backup, if the bucket was in a backup plan
- Glacier / lifecycle-transitioned copies
A deleted bucket name is released and can be re-created after a delay, but the data does not come back with it.
Next time
Turn on versioning plus MFA delete on anything that matters, and add a
lifecycle rule to expire old versions so it does not cost a fortune. Use
--dryrun — the AWS CLI supports it on every s3 rm — and prefer bucket
policies that deny s3:DeleteObject outright for application roles.
Terraform
terraform destroy
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Deletes every resource in the state file, in dependency order, in the real cloud account.
Also: terraform apply with a destructive plan · terraform state rm
Recovery
Stop the run now if it is still going — Ctrl-C once, and Terraform
finishes the current resource and stops. That can save the rest.
Then, per resource type:
- Databases: RDS may have a final snapshot if
skip_final_snapshot = false. Restore from it. - S3: covered by versioning (see the
aws s3 rmentry). - Anything with soft-delete: Azure Key Vault, GCS soft delete, Route53 zones can be re-created but with new nameservers.
- Everything else:
terraform applyrecreates the infrastructure from the same code. New IDs, new IPs, empty disks.
terraform state rm is the recoverable one — it only forgets a resource,
it does not delete it. Re-import with terraform import.
Next time
prevent_destroy = true in a lifecycle block on anything stateful. Never
run destroy without reading the plan count. Separate state files so that
production cannot be destroyed by a command aimed at staging.
GitHub
gh repo delete
🟡 Sometimes — Depends on what was configured beforehand. You have about 90 days.
What it does: The repository, its issues, PRs, releases, wiki and Actions history are deleted.
Also: Settings → Delete this repository · gh repo archive
Recovery
- Ask GitHub Support to restore it. Repositories are recoverable for
roughly 90 days after deletion, and support does this routinely:
https://support.github.com/contact — give them the exact
owner/repo. Do not create a new repo with the same name first; that blocks the restore. - The code itself is in every clone anyone has.
git pushit to a new remote and you have lost only the metadata. - Issues and PRs are the part you cannot rebuild from a clone. If you have any export, or a mirror on another forge, use it.
Next time
Archive instead of deleting — it is reversible and makes the repo read-only.
Keep a mirror: git clone --mirror on a cron job to any second location.
Package registries
npm publish
🟡 Sometimes — Depends on what was configured beforehand. You have about 72 hours.
What it does: Publishes to the public registry, permanently. Secrets in the tarball are now public and must be treated as compromised.
Also: npm publish --access public
Recovery
If it contains a secret, rotate the secret first. Unpublishing does not un-copy it — mirrors, caches and scrapers act within minutes. Assume it is public forever and revoke the key.
Then, within 72 hours of publishing, you may unpublish:
npm unpublish <pkg>@<version>After 72 hours npm only allows it if the package has no dependents and few downloads. The usual path instead is:
npm deprecate <pkg>@<version> "Published in error, use 1.2.4"
npm publish # a new, higher version that is correctThe version number is burned either way — npm never lets you reuse it.
Next time
npm publish --dry-run and read the file list. Use the files field in
package.json as an allowlist rather than .npmignore as a denylist — a
forgotten deny rule ships your .env, a missing allow rule just omits a file.
Editors & unsaved work
force quit with unsaved work
🟢 Yes — Reliably recoverable if you act.
What it does: The process dies without running its save-on-exit path.
Also: kill -9 editor · terminal closed · laptop died
Recovery
Most editors write recovery files continuously. Look before you retype:
Vim: a swap file sits next to the original. Reopen the file and Vim offers
Recover, or do it explicitly:vim -r file.txt ls -a .file.txt.swpVS Code: reopening usually restores unsaved buffers automatically. If not, the backups are on disk:
~/Library/Application Support/Code/Backups/ # macOS ~/.config/Code/Backups/ # LinuxJetBrains:
Local Historyon the file, which survives crashes.Office / Google Docs: version history, autosave, "Recover unsaved".
A shell heredoc or long command: check
~/.bash_history,~/.zsh_history, or your terminal's scrollback buffer before closing it.
Next time
Autosave on. It is a two-minute setting change that ends this category of loss permanently.
macOS
emptied the Trash
🟡 Sometimes — Depends on what was configured beforehand.
What it does: The files are unlinked. macOS does not keep a second copy.
Also: Shift-Delete · Empty Bin · secure empty trash
Recovery
- Time Machine — enter it and browse to the folder and date. This is the recovery that works, and the reason to have it switched on.
- iCloud Drive: iCloud.com → Account Settings → Restore Files. It keeps deleted files for 30 days independently of the local Trash.
- Dropbox / Google Drive / OneDrive: all keep deleted files for 30 days or more, in a separate web trash that emptying the local Trash does not touch.
- Photos and Notes have their own "Recently Deleted" albums, also 30 days, also unaffected by the Finder Trash.
- Undelete software is a long shot on an APFS SSD with TRIM enabled.
Next time
Time Machine to any external disk, plus one offsite copy. It is the only entry on this list that covers all of the others.
General
answered Yes to a destructive prompt
🟡 Sometimes — Depends on what was configured beforehand.
What it does: Varies. What matters is that you now need to know what it actually did.
Also: clicked through a warning · --yes · --force · -y
Recovery
Before trying to fix anything, find out what happened. Fixing the wrong thing makes recovery harder.
- Read the scrollback. Most destructive tools print what they removed. Copy it somewhere before the buffer scrolls away.
- Check the shell history for the exact command and its flags —
history | tail -20. - Check for logs:
~/.npm/_logs,/var/log/,journalctl --since "10 min ago", the cloud provider's audit trail (CloudTrail, Cloud Audit Logs, Azure Activity Log). Cloud audit logs are the single best source of truth for "what did I just delete" and almost nobody thinks to look. - Then find the specific entry in this repo for that command.
Next time
Never pass -y to a command you have not run without it first.
Contributing
One file in entries/. The bar is that you have actually done the recovery, or you cite someone who has. Plausible-sounding recovery steps are worse than none, because they get tried first and waste the window. See CONTRIBUTING.md.
Licence
Code MIT, content CC0.
Generated by scripts/build.mjs. Do not edit this file directly.
