- Book: Ship It — the pragmatic startup tech stack
- Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
"Reverting a merge commit declares that you will never want the tree changes brought in by the merge." That sentence sits in the git-revert manual, and it is the part of landing agent work that bites late.
You gave Claude Code a hotfix for release/2.4. It worked in its own worktree and said done. You merged and pushed. CI on the release branch went red. So you reverted the merge, let the agent fix its branch, and merged again. Only the agent's new fix came back. The rest of its work stayed reverted, because git still counts those commits as merged.
The agent's part is over at that point. What's left is yours: where it lands and how you back it out. Everything below is plain git and the GitHub CLI, so it works with any agent. The last section shows the same flow in Hermes IDE, which I build.
Step 1: write down the branch the task started from
Agents get started from all sorts of branches: main, a release branch, a teammate's feature branch. A week later nobody remembers which, and a hotfix lands in main instead of release/2.4.
Record the base when you create the agent's worktree. The GitHub CLI already reads a per-branch key for this: when you don't pass --base, gh pr create uses the branch's gh-merge-base config. Set it once, and both you and gh can read it back:
git worktree add -b agent/fix-login ../fix-login release/2.4
git config branch.agent/fix-login.gh-merge-base release/2.4
Before you land, check that the base hasn't moved since the agent forked. git merge-base --is-ancestor exits 0 when the first commit is an ancestor of the second:
base=$(git config branch.agent/fix-login.gh-merge-base)
git fetch origin
if git merge-base --is-ancestor "origin/$base" agent/fix-login
then
echo "$base has not moved"
else
git log --oneline agent/fix-login.."origin/$base"
fi
If the log prints commits, the base moved under the agent. Rebase the agent's branch onto it in its worktree (git -C ../fix-login rebase "origin/$base") and run the tests again. Green tests on a stale base tell you less than they seem to.
Step 2: pick squash or merge commit by how you'll undo it
Most squash-vs-merge arguments are about history. For agent work, pick by the undo you'll need.
A squash puts the whole branch on the base as one ordinary commit. The git-merge manual says --squash produces the merged tree but does not "make a commit, move the HEAD, or record $GIT_DIR/MERGE_HEAD", so you commit it yourself:
git switch release/2.4
git pull --ff-only
before=$(git rev-parse HEAD)
git merge --squash agent/fix-login
git commit -m "Fix flaky login check"
A merge commit keeps every agent commit and records the join. --no-ff creates one "even when the merge could instead be resolved as a fast-forward", so the landing is always a single commit you can point at:
before=$(git rev-parse HEAD)
git merge --no-ff agent/fix-login
Saving before costs one line and gives you an exact undo target. A merge also leaves the old tip in ORIG_HEAD (git-reset), but other commands overwrite it.
| Squash | Merge commit (--no-ff) |
|
|---|---|---|
| Lands as | One ordinary commit | Agent commits plus a merge commit |
| Undo, not pushed | git reset --keep "$before" |
git reset --keep "$before" |
| Undo, pushed | git revert <sha> |
git revert -m 1 <sha> |
| Re-land after a revert | Squash the fixed branch again | Revert the revert first |
The last row is the one from the opening. A squash never records the agent's commits as merged, so a fresh squash of the fixed branch brings all of its work back. A reverted merge commit is different, which Step 4 covers.

The undo path starts at the join: how the branch landed decides how you back it out.
One squash side effect: git branch -d only deletes a branch that is "fully merged", and a squashed branch never is. Keep it until CI on the base is green, since it is your source if you need to land again. Then delete it with -D.
Step 3: open the pull request from the terminal
If the base is shared, open a pull request instead of merging locally. CI runs on the PR before the change reaches anyone else. From the agent's worktree:
cd ../fix-login
git push -u origin agent/fix-login
gh pr create --base release/2.4 --head agent/fix-login \
--fill --draft
sha=$(gh pr view --json headRefOid -q .headRefOid)
gh pr checks --watch --fail-fast
gh pr create takes --base as "the branch into which you want your code merged". With gh-merge-base set in Step 1 the default is already right. Without it, gh falls back to the repository's default branch, so passing --base is cheap insurance. --fill uses the commit info for the title and body, and --draft keeps reviewers out until CI has had its say. gh pr view --json headRefOid reads the PR's head commit from GitHub before the checks start. gh pr checks --watch waits for the checks, and --fail-fast stops at the first failure.
When it's green, mark it ready and merge exactly the commit CI tested:
gh pr ready
gh pr merge --squash --match-head-commit "$sha"
--match-head-commit refuses the merge if the PR head is no longer that SHA. You read sha from GitHub before the checks ran, so it is the commit they ran against, not whatever your local checkout holds now. Agents push quickly, and this stops a commit nobody tested from slipping in between your check and your merge. If the base branch uses a merge queue, the same manual notes that no merge strategy is required. gh pr merge then enables auto-merge or adds the PR to the queue.
Step 4: CI on the base went red. Undo by how it landed
Pick the row that matches what happened.
Merged locally, not pushed. Reset to the commit you saved. --keep aborts instead of overwriting a file that has local changes:
git reset --keep "$before"
Squash pushed to the base. Revert the one commit and push. That is an ordinary new commit on the base:
git revert <squash-sha>
git push
PR merged on GitHub. Recent gh releases have gh pr revert, which opens a revert pull request from the terminal. GitHub's docs describe the same thing for the web UI: reverting "creates a new pull request that reverts the original merge commit". You need write access, and the revert goes through CI like any other PR:
gh pr revert 123 --title "Revert login fix: CI red"
Merge commit pushed to the base. Use -m 1 so git knows the base is the mainline:
git revert -m 1 <merge-sha>
git push
Then comes the trap from the opening. In the revert-a-faulty-merge how-to, Linus Torvalds explains that after the revert "the merge will still exist, and it will still be seen as joining the two branches together". When the agent fixes its branch on top, revert your revert first, then merge again:
git revert <revert-sha>
git merge --no-ff agent/fix-login
The how-to adds one exception. If the branch was rebuilt from scratch instead of fixed on top, skip the revert of the revert. It's one more reason to prefer squash for agent branches: a squash revert needs no follow-up step.
Step 5: the same flow in Hermes IDE
I build Hermes IDE, so weigh this section with that in mind. Hermes runs Claude Code and other CLI agents side by side, each task on its own hermes/<name> branch in its own worktree (README). Its Land step covers the base-branch, landing and local-undo parts of Steps 1 to 4.
Per the README and the release notes:
- Land commits, opens a pull request or merges locally in one step.
- It lands into the branch the task started from. That covers Step 1 without a config key.
- Your own checks can decide when a task is done.
- A landed task can be undone. Undo data for landed tasks is cleaned up after 14 days (2.2.9), so undo is for the days right after you land.

Commit, pull request or local merge, each into the branch the task started from.
Pushes from Hermes sign in with your own git credential helper or a GITHUB_TOKEN (README).
| Job | Plain git and gh | Hermes |
|---|---|---|
| Remember the base |
gh-merge-base config |
The branch the task started from |
| Tests before landing | Your CI, gh pr checks
|
Your own checks decide when a task is done |
| Land |
merge --squash or gh pr create
|
Land: commit, PR or local merge |
| Undo a local land | reset --keep "$before" |
Undo after landing |
One limit: whatever tool you land with, once a change is on a shared branch and other people have pulled it, I'd undo it the Step 4 way, with a revert that goes through CI. Hermes is free, source-available under BSL 1.1, runs on macOS, Windows and Linux, and is not affiliated with Nous Research, Anthropic, OpenAI or any agent vendor. 2.3.3 shipped on October 13, 2026.
If this was useful
Save Step 4 somewhere you'll find it when CI goes red, and land your next agent branch with squash and a saved before. If you'd rather have the base branch, the checks and the undo in one place, download Hermes IDE, and if it saves you a bad merge, star it on GitHub. A red base branch is a CI/CD problem as much as a git one. Ship It covers the rest of that path for a small team: hosting, deploys, CI/CD, auth, payments and monitoring, everything between localhost and production.

Top comments (0)