async-post-commit-hook-commit-orphaned-by-squash-merge · v1.0.0 · 2026-07-08 · sha256 f8f86593796d669a
async-post-commit-hook-commit-orphaned-by-squash-merge v1.0.0A
Immutable. This exact content is served forever at /api/v1/blob/f8f86593796d669a.
---
name: async-post-commit-hook-commit-orphaned-by-squash-merge
description: |
In a repo with an ASYNC/background post-commit hook (one that fires after a
commit and creates its OWN follow-up commit — `[auto-docs] …`, a docs/site
regen, a changelog/checkbox tick), the hook's commit can land LOCAL-ONLY
*after* your `git push` already captured just your work commit, so it is never
in the PR and gets ORPHANED + silently lost when the squash-merged branch is
deleted. Use when: (1) you pushed a fix, PR'd it, and squash-merged, then a
later `git status -sb` shows your feature branch is `[ahead 1]` (or ahead N)
of its `origin/<branch>` tracking ref even though "everything merged"; (2) that
extra commit is authored by a background hook (message prefix `[auto-docs]`,
`chore: regenerate …`, no human author intent) and contains REAL content (a
corrected design/doc, a regenerated site asset); (3) you're about to delete the
merged branch / move on and would lose it. The cause: the hook runs
asynchronously ("running doc update in background…"), so `git push -u origin`
completes BEFORE the hook commits — the push and the squash-merge both see only
your work commit. The fix: BEFORE deleting the branch, inspect the orphan
(`git show <sha>`), and if it carries content worth keeping, cherry-pick it
onto a fresh branch off origin/main and fold it into your next (e.g. handoff)
PR. Sibling of git-amend-hits-async-post-commit-hook-commit (amend folds into
the hook commit), git-add-u-after-async-post-commit-hook,
git-rebase-stalls-async-post-commit-hook, git-pull-after-squash-merge.
author: Claude Code
version: 1.0.0
date: 2026-06-22
disable-model-invocation: true
---
# Async post-commit hook's commit is orphaned by a squash-merge
## Problem
A repo has an **async post-commit hook** that fires in the background after each
commit and creates its OWN follow-up commit — typically auto-generated docs, a
site regen, or a "correct the design doc to match the code you just changed"
edit (you'll see `[post-commit] … running doc update in background…` at commit
time and an `[auto-docs] …` commit appear seconds-to-minutes later).
The race that loses content:
1. You commit your fix (`feat/fix …`).
2. `git push -u origin <branch>` runs — and **completes before the async hook
finishes**, so the remote has only your fix commit.
3. You open the PR and squash-merge it. The squash-merge is built from the
pushed remote state → it contains only your fix commit.
4. *Then* the background hook's `[auto-docs]` commit lands — **local-only**, on
top of your branch. It's never pushed, never in the PR, never on main.
Now your local branch is `[ahead 1]` of `origin/<branch>`, holding a commit with
real, useful content (e.g. a design doc corrected to match your change). If you
delete the merged branch or just start the next task, that content is **silently
lost** — and because it was the *hook's* job to keep the doc in sync, nobody
re-generates it.
## Context / Trigger Conditions
- The repo has a background/async post-commit hook (greppable: a `post-commit`
hook that backgrounds work; commits prefixed `[auto-docs]` / `chore: regenerate`
with no human author intent).
- You pushed → PR'd → **squash-merged** a branch this session.
- `git status -sb` on that (still-checked-out) branch shows `## <branch>...origin/<branch> [ahead 1]`
(or ahead N) even though you believe it fully merged.
- `git log --oneline` shows the top local commit is the hook's, authored after
your work commit, and it touches files (a design doc, a site asset) — not empty.
## Solution
**Before deleting the branch or moving on, rescue the orphan.**
1. **See it.** On the just-merged branch:
```sh
git status -sb # "[ahead 1]" of origin = unpushed local commit(s)
git log --oneline -3 # top one is the [auto-docs]/hook commit?
git show <sha> --stat # what content did the hook generate?
```
2. **Judge it.** If the orphan is genuinely valuable (a doc corrected to match
your merged change, a needed regen) → rescue. If it's noise (a no-op regen,
already-correct) → discard, no action.
3. **Confirm the target still exists on main** (main may have moved many commits
since your fix):
```sh
git cat-file -e origin/main:<path-the-hook-edited> && echo EXISTS
```
4. **Rescue by cherry-pick onto a fresh branch off origin/main** (NOT onto the
stale/merged branch), and fold it into your next PR — the session-handoff PR
is the natural home:
```sh
git fetch origin
git checkout -b <handoff-branch> origin/main
git cherry-pick <sha> # the [auto-docs] commit applies cleanly
# … add handoff docs, commit, PR, merge
```
## Verification
- `git show <new-sha> --stat` on the cherry-picked commit shows the same file(s)
as the orphan.
- The next PR's diff includes the rescued content; after merge,
`git show origin/main:<path>` contains the hook's correction.
- The orphan is no longer unique: `git branch --contains <original-sha>` is now
moot because the *content* (not the sha) is on main via the cherry-pick.
## Example (the-project-repo, 2026-06-22)
Shipped a /monitor copy fix: committed `7c14b4e0`, `git push -u origin
monitor-sprwin-card-honesty`, PR #1283, squash-merged to `d47349e6`. During the
session-handoff, `git status -sb` showed the branch `[ahead 1]` of origin. The
extra commit `c5de20d3` was `[auto-docs] monitor-redesign design: correct SprWin
empty-card framing to data gap` — the post-commit hook had rewritten the
canonical design doc (`docs/plans/2026-06-19-monitor-redesign/design.md`) so a
future session wouldn't follow the now-falsified "add rich SprWin SQL"
recommendation. Real, load-bearing content — and it was never in PR #1283
(the push captured only `7c14b4e0`; the hook committed after). Rescued: branched
`s262b-followup-handoff` off origin/main (which was 12 commits ahead by then),
`git cherry-pick c5de20d3` (applied clean — the design doc still existed on
main), and shipped it in the handoff PR #1299.
## Notes
- **The tell is `[ahead 1]` after a "complete" squash-merge.** A squash-merge
normally leaves the local branch behind/diverged, not *ahead* of its own
origin ref. "Ahead" means a local commit never made it to the remote — and if
you didn't author it, the async hook did.
- **Why the push misses it:** the hook is async by design (it says "running … in
background"). `git push` does not wait for it. Whether the hook commit makes
the push is a pure race; for a fast push it almost never does.
- **Distinct from the sibling skills:** `git-amend-hits-async-post-commit-hook-commit`
is the hook commit corrupting an `--amend`; `git-pull-after-squash-merge` is an
untracked-file checkout conflict. This skill is the hook commit being *orphaned*
(its content lost) because the push/PR/squash-merge all predate it.
- Don't blind-delete a merged branch that's `[ahead]` of origin. Inspect the
ahead commits first — same discipline as "read before archive/delete".