# git reflog expire: Recover and Prune the Reflog

> git reflog records where HEAD has been so you can recover "lost" commits, and reflog expire prunes it. Reference for recovery, expire flags, and CI cleanup.

Source: https://latchkey.dev/learn/command-reference/git-reflog-expire  
Updated: 2026-06-30

git reflog lists every position HEAD has held, so a commit dropped by reset or rebase is recoverable; git reflog expire removes old entries and can force gc to drop them.

The reflog is the undo buffer for history surgery. Knowing how to read it recovers work after a bad reset; knowing how to expire it matters on persistent runners where stale reflogs keep dead objects alive and bloat .git.

## What it does

Each branch and HEAD has a reflog of its recent tip positions. git reflog (or git reflog show HEAD) lists them as HEAD@{n}. You recover by resetting to one. git reflog expire deletes entries older than a cutoff; with --expire-unreachable it targets entries whose commits are no longer reachable, freeing them for gc.

## Common usage

```Terminal
git reflog
git reset --hard HEAD@{2}        # undo the last two HEAD moves
# expire entries to let gc reclaim dropped commits now
git reflog expire --expire=now --all
git reflog expire --expire-unreachable=now --all
git gc --prune=now
```

## Options

| Flag | What it does |
| --- | --- |
| show | Print the reflog (default subcommand) |
| expire --expire=<time> | Remove entries older than <time> (e.g. now, 2.weeks) |
| --expire-unreachable=<time> | Target only entries pointing at unreachable commits |
| --all | Apply to every ref, not just one |
| delete <ref>@{n} | Remove a single reflog entry |
| --rewrite / --updateref | Adjust surrounding entries / move the ref |

## In CI

Fresh clones in CI have an empty reflog, so do not rely on HEAD@{n} surviving across jobs. On a persistent or cached runner, "lost" commits stay on disk because the reflog references them; git reflog expire --expire=now --all followed by git gc --prune=now actually frees the space.

## Common errors in CI

"fatal: Log for 'HEAD' only has N entries" means you asked for HEAD@{n} beyond what exists, common on shallow or fresh clones. "fatal: ambiguous argument 'HEAD@{2}'" with no reflog means core.logAllRefUpdates is off (bare repos disable reflogs by default).

## Using this in CI

CI checkouts are shallow and detached by default, which changes the answer this command gives you. Commands that read history, branch names, or tags need the checkout configured for it.

```.github/workflows/ci.yml
- uses: actions/checkout@v4
  with:
    fetch-depth: 0   # history, tags, and git describe all need this

- run: |
    git rev-parse --is-shallow-repository   # expect false
    git rev-parse --abbrev-ref HEAD          # prints HEAD when detached
```

> `git rev-parse --abbrev-ref HEAD` returns the literal string `HEAD` on a detached checkout rather than a branch name. On GitHub Actions read `github.ref_name` instead; the git command cannot know what it was checked out for.

## FAQ

### git reflog expire: Recover and Prune the Reflog?

The reflog is the undo buffer for history surgery. Knowing how to read it recovers work after a bad reset; knowing how to expire it matters on persistent runners where stale reflogs keep dead objects alive and bloat .git.

### What it does?

Each branch and HEAD has a reflog of its recent tip positions. git reflog (or git reflog show HEAD) lists them as HEAD@{n}. You recover by resetting to one. git reflog expire deletes entries older than a cutoff; with --expire-unreachable it targets entries whose commits are no longer reachable, freeing them for gc.

### In CI?

Fresh clones in CI have an empty reflog, so do not rely on HEAD@{n} surviving across jobs. On a persistent or cached runner, "lost" commits stay on disk because the reflog references them; git reflog expire --expire=now --all followed by git gc --prune=now actually frees the space.

### Common errors in CI?

"fatal: Log for 'HEAD' only has N entries" means you asked for HEAD@{n} beyond what exists, common on shallow or fresh clones. "fatal: ambiguous argument 'HEAD@{2}'" with no reflog means core.logAllRefUpdates is off (bare repos disable reflogs by default).

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
