git reset --soft HEAD~1 removes the commit and leaves every change staged, exactly as it was a moment before you committed. That's the answer for the common case. Which case you're in matters more than the flag.
1. Not pushed yet — undo the commit, keep the work staged
$ clai undo my last commit but keep the changes staged→ git reset --soft HEAD~1$ git status --shortM f.txt
The commit is gone from the log, the files are untouched, everything is still in the index. Fix the message or add the file you forgot, then commit again.
2. Keep the changes but unstage them
$ clai undo the last commit and unstage the changes too→ git reset HEAD~1
--mixed is the default. Same as soft, one step further back: files stay on disk, index is cleared. Use it when you want to re-split the work into different commits.
3. Throw the work away entirely
$ clai delete my last commit and all its changes permanently→ git reset --hard HEAD~1⚠ DANGER — uncommitted changes in the working tree are destroyed
--hard is the only one of the three that loses work. Committed content is recoverable through the reflog; anything you had modified but not committed is not.
4. Already pushed — revert instead of reset
$ clai undo the commit I already pushed, without rewriting history→ git revert HEAD
revert makes a new commit that undoes the old one. History stays intact, so nobody who already pulled has to do anything. On a shared branch this is the only correct answer.
5. Just fix the message
$ clai change the message of my last commit→ git commit --amend -m "Fix cache invalidation on write path"
If nothing is staged, --amend rewrites only the message. If something is staged, it folds those changes into the previous commit too — useful, but easy to do by accident.
6. You picked wrong — get it back
$ clai show me where HEAD was before that reset→ git reflog4d2e830 HEAD@{0}: reset: moving to HEAD~1dc4fa62 HEAD@{1}: commit: second4d2e830 HEAD@{2}: commit (initial): first$ git reset --hard dc4fa62
The reflog remembers every position HEAD has held for 90 days by default. A --hard reset onto a lost commit brings it back exactly as it was.
Gotchas
- Never force-push a reset to a shared branch. Rewriting history that others have pulled breaks their clones.
revertexists for exactly this. HEAD~1andHEAD^are the same here — one commit back. They differ only on merge commits, whereHEAD^2means the second parent.--amendafter pushing also rewrites history. It creates a new commit object with a new hash, so the same rule applies.
Related questions
How do I undo the last three commits but keep all the changes? git reset --soft HEAD~3 — everything from those commits ends up staged together.
Does reset delete the commit permanently? Not immediately. It becomes unreachable and the reflog still points at it for ~90 days, until garbage collection runs.
Reset or revert on a feature branch nobody else uses? Reset. It's cleaner. Revert as soon as somebody else might have pulled it.
See also
- Git tasks you say out loud: "show me commits I haven't pushed"
- SAFE, CAUTION, DANGER: how CliAI classifies what's about to run
- Onboarding day 1: how CliAI replaces "ask the senior on Slack"
CliAI labels reset --hard as DANGER and asks for a typed confirmation — the one git command that actually loses work. Install it in one line.