Git History Surgery skill for Codex
Rewrite history with a backup and a proof. Codex rewrites history with recipes that need no interactive editor: autosquash for fixups, GIT_SEQUENCE_EDITOR and GIT_EDITOR scripting for reword and edit, cherry-pick rebuilds for reordering, and reset --keep for misplaced commits. Each rewrite starts with a backup branch and ends with an empty tree diff plus range-diff, and force pushes wait for your explicit go-ahead.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- git-history-surgery Installs to ~/.agents/skills/git-history-surgery/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install git-history-surgery The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project git-history-surgery Installs to .agents/skills/git-history-surgery/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/git-history-surgery.zipunzip -o git-history-surgery.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) git-history-surgery PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $git-history-surgery, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Rewrites local git history precisely without an interactive editor - fixup commits with autosquash, scripted rebase todos via GIT_SEQUENCE_EDITOR for reword, reorder, drop and edit, splitting a commit, squashing a branch with reset --soft, moving commits between branches, rebasing stacks with --update-refs, and rescuing lost work from the reflog or fsck. Every rewrite starts with a backup ref and ends with a range-diff check. Use when asked to squash, reorder, reword or split commits, clean a branch before merge, undo a bad rebase or reset, or recover deleted commits.
Use it when
- A branch needs squashing, reordering or rewording before merge
- One commit mixes changes that should be separate
- Work seems lost after a reset, rebase or deleted branch
Use something else when
- Commits do not exist yet and you are deciding how to group changes: use git-commit-hygiene
- A rebase stopped on conflicts you need to resolve: use merge-conflict-resolution
How to check the result
git diff between the backup branch and HEAD is empty for history-only rewrites, git range-diff shows the intended commit mapping, and rebase --exec passes on every commit.
The mistake it prevents
Running git rebase -i in an agent shell with no sequence editor configured, which hangs waiting for an editor that never opens.
A rewrite is correct when the final tree is exactly what was intended (usually identical to the pre-rewrite tree), each resulting commit is coherent and builds, a backup ref makes the operation reversible in one command, and nothing shared was rewritten without the user’s consent.
Safety protocol (every time)
git status --short # must be clean; stash or commit first
git log --oneline --graph -15
git branch backup/$(git branch --show-current)-$(date +%Y%m%d-%H%M%S)
git rev-parse --abbrev-ref @{upstream} 2>/dev/null && git log --oneline @{upstream}..HEAD
- If the commits you will rewrite are already on a remote branch others use, stop and ask. Rewriting them forces everyone else to recover.
- Know your base: usually
git merge-base HEAD origin/main. Use it explicitly rather than countingHEAD~nby eye. - Codex cannot drive an interactive editor. Use the non-interactive recipes below;
never run a bare
git rebase -iwithoutGIT_SEQUENCE_EDITORset.
Recipe chooser
| Goal | Recipe |
|---|---|
| Fold a correction into an earlier commit | Fixup + autosquash |
| Squash the whole branch into one commit | reset --soft |
| Change an old commit message | Scripted reword |
| Reorder or drop commits | Cherry-pick onto a fresh branch, or rebase --onto |
| Split one commit into several | Scripted edit, then reset and re-commit |
| Commits landed on the wrong branch | Branch + reset --keep |
| Rebase a stack of dependent branches | rebase --update-refs |
| Undo a rebase, reset or amend | Reflog |
| Remove a file or secret from all history | git filter-repo (see hard rules) |
Recipes
Fixup + autosquash
git add -- src/cart.py
git commit --fixup=<target-sha> # creates "fixup! <target subject>"
BASE=$(git merge-base HEAD origin/main)
git rebase --autosquash "$BASE" # Git 2.44+: works without -i
GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash "$BASE" # older Git: accept the generated todo
--fixup=amend:<sha> also replaces the target’s message, but it opens an editor; use
the scripted reword below instead.
Squash the whole branch
BASE=$(git merge-base HEAD origin/main)
git reset --soft "$BASE"
git commit -F "$TMPDIR/squashed-msg.txt"
Write the message from the combined intent, not by concatenating old subjects.
Reword an older commit
BASE=$(git merge-base HEAD origin/main)
SHA=$(git rev-parse --short <target>)
printf 'Cap bulk discount at a single tier\n\nTiers stacked, so 100+ unit orders were undercharged.\n' > "$TMPDIR/msg.txt"
GIT_SEQUENCE_EDITOR="sed -i.bak 's/^pick $SHA/reword $SHA/'" \
GIT_EDITOR="cp $TMPDIR/msg.txt" \
git rebase -i "$BASE"
GIT_SEQUENCE_EDITOR edits the todo list; GIT_EDITOR is called with the message file
path as its last argument, so cp overwrites it with your text. sed -i.bak works on
both GNU and BSD sed. For the most recent commit, git commit --amend -F msg.txt is enough.
Reorder or drop
Drop one commit:
git rebase --onto <bad-sha>^ <bad-sha>
Reorder transparently by rebuilding the branch with cherry-pick:
git switch -c rebuilt "$BASE"
git cherry-pick <sha-C> <sha-A> <sha-B> # in the new order
git diff backup/<branch>-<stamp> rebuilt # empty: same final tree
git switch <branch> && git reset --hard rebuilt # confirm with the user before --hard
Reordering can create conflicts when later commits depend on earlier ones; resolve with merge-conflict-resolution, or reconsider the order.
Split a commit
For HEAD:
git reset HEAD~ # changes back to working tree, unstaged
git add -- src/tax.py && git commit -m "Extract tax calculation"
git add -- src/cart.py tests/ && git commit -m "Cap bulk discount at a single tier"
For an older commit, stop the rebase at it:
GIT_SEQUENCE_EDITOR="sed -i.bak 's/^pick $SHA/edit $SHA/'" git rebase -i "$BASE"
git reset HEAD~
# stage and commit in pieces; for partial files use a patch with git apply --cached
git rebase --continue
Within one file, stage hunks non-interactively with git diff file > p.patch, trim to
the wanted hunks, then git apply --cached --recount p.patch (git-commit-hygiene covers
this in detail).
Commits on the wrong branch
git branch feature/discounts # new branch keeps the 3 commits
git reset --keep HEAD~3 # move current branch back; refuses if it would lose local edits
git switch feature/discounts
Stacked branches
git rebase --update-refs origin/main # rebases the branch and moves every branch ref inside the range
Set git config rebase.updateRefs true per repo if the user works in stacks; see
stacked-pull-requests for the full workflow.
Rescue
Committed work is rarely lost: reflog entries are kept 90 days by default, or 30 days
for commits no longer reachable from any branch (gc.reflogExpire, gc.reflogExpireUnreachable).
git reflog --date=relative -20 # where HEAD has been
git reflog show <branch> -10 # where a branch pointed
git branch rescue/<name> <sha> # pin a found commit before doing anything else
git rebase --abort # mid-rebase: back to where you started
git reset --keep ORIG_HEAD # undo the last rebase/reset/merge when the tree is clean
Commits never referenced by a reflog (a dropped stash, a deleted branch from a fresh clone) can still be found until garbage collection:
git fsck --unreachable --no-reflogs | awk '/commit/ {print $3}' \
| xargs -n1 git log -1 --format='%h %cr %s'
git stash apply <sha> # a dropped stash shows up as a commit "WIP on ..."
Pin anything valuable with a branch immediately; git gc may prune unreachable objects.
Verify the rewrite
git diff backup/<branch>-<stamp> HEAD # empty when only the history changed
git range-diff "$BASE" backup/<branch>-<stamp> HEAD # commit-by-commit comparison
git log --oneline "$BASE"..HEAD
git rebase --exec "<test cmd>" "$BASE" # every commit still builds and passes
If git diff against the backup is not empty and you did not intend a content change,
something was lost or duplicated during conflict resolution. Restore with
git reset --keep backup/<branch>-<stamp> and redo.
Delete the backup branch only after the user confirms the result.
Hard rules
- Force pushing is destructive for collaborators. Only with explicit user confirmation,
and only as
git push --force-with-lease --force-if-includes, never plain--force. git reset --hard,git clean -fdandgit checkout -- .discard uncommitted work. Confirm with the user and prefer--keep, which refuses to overwrite local changes.- Removing secrets or large files from all history needs
git filter-repo(a separate install that needs network approval), rewrites every commit SHA, and requires every clone to be re-cloned. Secrets must be rotated regardless; history rewriting does not un-leak them. Get the user’s explicit decision first. - Never rewrite
main/masteror release branches.
Report
Rewrote feature/bulk-pricing (local only, not pushed):
before: 7 commits (3 fixups, 1 WIP) after: 3 commits
backup: backup/feature-bulk-pricing-20261028-1412
git diff backup HEAD: empty (history-only change)
every commit passes `pytest -q` (rebase --exec)
To publish: git push --force-with-lease --force-if-includes (awaiting your OK) What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Git History Surgery"
short_description: "Rewrite history with a backup and a proof"
default_prompt: "$git-history-surgery Squash my fixup commits into the right commits before I merge" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.