Take a breath. A force push feels like you just deleted production, but in git almost nothing is truly deleted right away. The commits you wiped are probably still sitting on GitHub's servers — unreachable, but intact.
The catch: they're on a clock. GitHub's garbage collector eventually removes commits nothing points to. The window is undocumented and measured in weeks, but treat it like hours and you'll never be sorry.
Case 1: you have the commits locally
Best case, and the most common one — you force-pushed from your machine, so your local clone still has everything.
git reflog
Find the line where your branch pointed at the good commit (reflog lists every move HEAD made, newest first). Then put the branch back:
git branch recovery-branch abc1234
git push origin recovery-branch
You now have the lost state on GitHub under a new name, and you can merge or cherry-pick from it at your leisure.
Case 2: you don't have the commits locally
Someone else pushed the commits, or you wiped them from a machine you no longer have. Reflog can't help you — it only knows your clone's history. What you need is the SHA of a lost commit, and there are three places one usually survives:
A pull request. If the commits were ever in a PR, GitHub keeps them alive under the PR's hidden refs even after the source branch is gone. Open the PR — the commits are listed right there, SHAs included.
GitHub's activity feed. The repo's Activity view (and its API equivalent) records force pushes, including the SHA the branch pointed at before the push. That's exactly the commit you want back.
CI logs. Any build that checked out the branch printed the commit SHA it built. Scroll back to a build from before the force push.
Once you have a SHA, check that GitHub still has the object. The quickest way from a browser:
https://github.com/<owner>/<repo>/commit/<sha>
If the page loads, the commit is alive. Create a branch pointing at it (the GitHub API's "create ref" endpoint does this without any local clone), and you've recovered.
Scan the wreckage with Rescue
Rescue scans your repo's GitHub activity for force pushes and branch deletions, checks which lost commits are still alive on GitHub's servers, and restores the ones that are onto a new branch. No local clone needed.
Open Rescue →Why the recovered work should live on a new branch
Notice every path above ends with "create a new branch at the old SHA" — never "force push the branch back." Two reasons. First, whatever state the branch is in now may contain work you'd lose by blasting over it. Second, a recovered branch can be compared, reviewed, and merged like any other change. Recovery should leave a paper trail, not a second incident.
Make the next one boring
Force pushes happen. What turns them into disasters is having no backup and no idea where to look. Two cheap habits: use --force-with-lease so git refuses to clobber commits you haven't seen, and take a save point before any history rewrite so "recovery" becomes "reset to the bookmark."