Landing an Agent Branch and Rolling It Back Cleanly: A Git Playbook for When CI Disagrees

A branch landing and clean rollback workflow for AI coding agents: prefer squash, record the base branch, merge precisely, and revert on shared branches.
A landing and rollback workflow for AI coding agent branches, drawn from git docs, the GitHub CLI manual, and Linus Torvalds' revert-a-faulty-merge how-to. Record the base branch (gh-merge-base), check for a moved base with merge-base --is-ancestor, prefer squash over merge commits for easy rollback, merge exactly the CI-tested commit with gh pr merge --match-head-commit, and revert through CI once the change is on a shared branch.
When an AI coding agent (like Claude Code) finishes a task and merges its code into a release branch, only for CI to suddenly turn red — this is a scenario many developers have run into. The trickier part is the rollback: if you directly revert a merge commit, git considers those commits to have already been merged, so when the agent's fix branch is merged again later, only the new fix comes in, and everything else remains in the reverted state.
This article distills a landing and rollback workflow that works for any AI agent, based on the official git documentation, the GitHub CLI manual, and Linus Torvalds' how-to on reverting a faulty merge.

Step 1: Record the base branch when the task starts
The agent may have started from main, a release branch, or a teammate's feature branch. A week later nobody remembers which base branch it was, and the hotfix might land on main instead of release/2.4 by mistake.
Record the base branch when you create the agent's worktree. The GitHub CLI already reads a config key per branch for exactly 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 landing, check whether the base branch has moved since the agent forked. git merge-base --is-ancestor exits with code 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 branch moved while the agent was working. You need to rebase in the agent's worktree (git -C ../fix-login rebase "origin/$base") and rerun the tests. Green tests on a stale base are far less trustworthy than they appear.
On git merge-base --is-ancestor and the "stale base" problem
git merge-base --is-ancestor A B determines whether commit A is an ancestor of commit B: exit code 0 if yes, 1 if no (a non-zero code doesn't mean an error — you need to distinguish it from a real failure). In scripts, it's often used as a boolean check for "is the snapshot point still reachable."
"The base branch moved" means: after the agent forked its work branch from some commit, the base branch advanced with new commits. The agent's tests were run against the old code, so even if they're all green, they can't guarantee compatibility with the latest base-branch code — especially if someone changed the same file or the same interface. So before landing you must check the ancestor relationship, and as soon as you detect movement, rebase onto the latest origin/$base and rerun the tests. This check is the key gate against "false greens" and is far cheaper than rolling back afterward.
Step 2: Choose squash or merge commit based on how you'll roll back
Most debates about squash vs. merge revolve around history. For agent work, you should choose based on the rollback you need.
A squash puts the entire branch onto the base branch as a single ordinary commit. The git-merge manual notes that --squash produces the merged tree but does not "create a commit, move HEAD, or record $GIT_DIR/MERGE_HEAD," so you have to commit 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 preserves every agent commit and records the join point. --no-ff creates a merge commit even when the merge could have been resolved by fast-forward, so the landing is always a commit you can point to precisely:
before=$(git rev-parse HEAD)
git merge --no-ff agent/fix-login
Saving before takes one line but gives you a precise rollback target. The merge also leaves the old tip in ORIG_HEAD, but other commands overwrite it.
| Squash | Merge commit (--no-ff) | |
|---|---|---|
| Landing form | One ordinary commit | Agent commits plus a merge commit |
| Rollback (unpushed) | git reset --keep "$before" | git reset --keep "$before" |
| Rollback (pushed) | git revert <sha> | git revert -m 1 <sha> |
| Re-land after revert | Squash the fixed branch again | First revert that revert |
That last row is exactly the trap mentioned at the start. Squash never records the agent's commits as merged, so squashing the fixed branch again brings back all the work. A reverted merge commit is different — Step 4 explains in detail.

One side effect of squash: git branch -d only deletes "fully merged" branches, and a squashed branch never is. Keep it around until CI on the base branch goes green, because it's your source for re-landing. Then delete it with -D.
The semantics of --squash, --no-ff, and ORIG_HEAD
git merge --squash only integrates the other branch's changes into the working tree and staging area — it produces no commit and doesn't update MERGE_HEAD or HEAD, so you must git commit manually. The result is an "ordinary commit" on the base branch, and the history no longer retains the connection information about "which branch this batch of changes came from."
git merge --no-ff forces a two-parent merge commit, even when a fast-forward would have been possible. Its value is that the merge point is a clear, referenceable commit, and a later git revert -m 1 can precisely "undo the integration of an entire branch."
Either way, running git merge writes the current HEAD into ORIG_HEAD before merging, so ORIG_HEAD can serve as a temporary rollback target; but it gets overwritten by commands like rebase and reset, so the article recommends manually saving before=$(git rev-parse HEAD) into a variable or file.
Step 3: Open a pull request from the terminal
If the base branch is shared, you should open a pull request rather than merging locally. CI runs on the PR, validating the change before it reaches anyone else. In 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 treats --base as "the branch you want the code merged into." After setting gh-merge-base in Step 1, the default is already correct. Without it, gh falls back to the repository's default branch, so passing --base is cheap insurance. --fill uses the commit messages as the title and body, and --draft keeps reviewers out until CI has weighed in. 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 on the first failure.
Once 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 to merge when the PR head is no longer that SHA. Because you read sha from GitHub before the checks ran, it's the commit the checks targeted, not whatever your local checkout currently holds. Agents push fast, and this stops untested commits from slipping in between your checks and your merge. If the base branch uses a merge queue, gh pr merge enables auto-merge or adds the PR to the queue.
--match-head-commit and headRefOid
On GitHub, a PR's "head commit" is headRefOid, the SHA the branch currently points to. CI checks run against a specific SHA, not against the concept of "the branch." Agents push very fast and may push a new commit between the checks completing and the merge, so what actually gets merged could be untested code.
gh pr merge --match-head-commit <sha> is exactly this anti-tampering mechanism: if the PR head no longer equals the given SHA at merge time, the command refuses to merge. Combined with reading gh pr view --json headRefOid first, it guarantees that "the commit the checks targeted" and "the commit that ultimately gets merged" are the same. This is especially useful with GitHub's merge queue, since the queue runs a final check before merging.
Step 4: Base-branch CI turns red — roll back based on how you landed
Pick the row that matches your situation.
Local merge, unpushed. Reset to the commit you saved. --keep aborts rather than overwrites when there are local file changes:
git reset --keep "$before"
Squash already pushed to the base branch. Revert that one commit and push. This is an ordinary new commit on the base branch:
git revert <squash-sha>
git push
PR already merged on GitHub. Newer gh versions have gh pr revert, which opens a revert pull request from the terminal. GitHub's docs describe the Web UI the same way: a revert "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 already pushed to the base branch. Use -m 1 so git knows the base branch is the mainline:
git revert -m 1 <merge-sha>
git push
And then the trap from the start. In Linus Torvalds' revert-a-faulty-merge how-to, he explains that after a revert "the merge still exists, and will still be seen as joining the two branches together." When the agent fixes the issue on its branch, first revert your revert, 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 rather than fixed on top, skip reverting the revert. This is another reason to prefer squash for agent branches: a squash revert needs no follow-up step.
Step 5: The same workflow in Hermes IDE
Hermes IDE runs Claude Code and other CLI agents side by side, with each task on its own hermes/<name> branch and its own worktree. Its Land step covers the base-branch, landing, and local-rollback parts of Steps 1 through 4. According to the README and release notes:
- Commit, open a pull request, or merge locally in one step.
- It lands to the branch the task started from. This covers Step 1, with no config key needed.
- 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 works within a few days of landing.

Pushes from Hermes authenticate using your own git credential helper or GITHUB_TOKEN.
| Task | Plain git and gh | Hermes |
|---|---|---|
| Remember the base branch | gh-merge-base config | The branch the task started from |
| Test before landing | Your CI, gh pr checks | Your own checks decide when a task is done |
| Landing | merge --squash or gh pr create | Land: commit, PR, or local merge |
| Undo a local landing | reset --keep "$before" | Undo after landing |
One limitation: no matter what tool you use to land, once the change is on a shared branch and others have pulled it, roll back the way Step 4 describes — via 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.
On Hermes IDE and BSL 1.1
Hermes IDE is a parallel work environment for AI coding agents: it creates a separate hermes/<name> branch and git worktree for each task, letting multiple CLI agents (like Claude Code) run in parallel without interfering with each other. This maps directly to the "record the base branch" emphasis in Step 1 — because the worktree itself is tied to the fork point, no extra config key is needed.
Its licensing model is BSL 1.1 (Business Source License 1.1): the source is available, but commercial use is limited by additional terms set by the licensor; typically it converts to an open-source license after a change date. It's free, but not open source in the traditional OSI sense. Its "Land" and "Undo" cover local landing and rollback, but once a change reaches a shared branch you should still return to Step 4 and roll back via the CI workflow.
Key Takeaways
For agent work, choose your landing method based on rollback convenience rather than history aesthetics. Squash is the preferred choice for agent branches, because reverting a squash commit requires no follow-up revert-the-revert step. Whatever tool you use, once the code is on a shared branch, roll back through the CI workflow rather than a local force reset.
One-line Summary
A branch landing and clean rollback workflow for AI coding agents: prefer squash, record the base branch, merge precisely, and revert on shared branches.
Paragraph Summary
The article addresses the common scenario where an AI coding agent finishes a task and merges, only for CI to turn red, distilling a landing and rollback workflow from the official git docs, the GitHub CLI manual, and Linus Torvalds' how-to on reverting a faulty merge. The core idea: choose your landing method based on rollback convenience rather than history aesthetics. First, record the base branch when the task starts and persist it with the gh-merge-base config; before landing, use merge-base --is-ancestor to check whether the base branch has moved, avoiding "false green" tests on a stale base. Second, prefer squash over merge commit for agent branches, because a reverted squash commit can be brought back entirely by squashing the fixed branch again, whereas a reverted merge commit requires reverting the revert before merging again. Third, use gh pr merge --match-head-commit to ensure you merge exactly the commit CI tested. Finally, once a change has been pushed to a shared branch, roll back through a revert + CI workflow rather than a local force reset.
Related articles

DeepCell: One-Click AI Generation of Documents, Spreadsheets, and Slides
DeepCell tops Product Hunt with one-click generation of documents, spreadsheets, and slides, using the .deepcell format for linked updates, designed for System 2 tasks like modeling and sales reviews.

Doneas: 15-Minute Daily Accountability Calls That Keep Indie Developers From Procrastinating
Doneas is a daily accountability tool for indie developers: plan tasks on Monday, join a 15-minute call with five builders each day, and log deliveries on a public progress page. This article analyzes its core mechanics, effectiveness, and potential issues around retention and matching.

Fastlane 2.0: Fully Automated AI Ad Campaigns, From Creative to Optimization
Fastlane 2.0 is an AI ad automation tool: enter your website and it learns your brand, generates video and image ads, and runs and optimizes them on Meta, TikTok, Instagram, and YouTube.