DEV Community

Cover image for Task B Needs Task A's Branch: Stack Two Agents' Work With git rebase --update-refs
Gabriel Anhaia
Gabriel Anhaia

Posted on Edited on AI-assisted

Task B Needs Task A's Branch: Stack Two Agents' Work With git rebase --update-refs


You hand Claude Code the API change: add an orderTotal() function. You hand Codex the UI change that shows the total. Both agents get their own branch cut from main, the way most worktree guides set it up.

Codex finishes first. Its branch imports a function that does not exist on main, so its tests fail, and the obvious fix for a missing function is to write one. So it does. Now you have two orderTotal() implementations on two branches, and a merge conflict waiting for whoever lands second.

The tasks were never independent. The UI change depends on the API change, so its branch should start from the API branch, not from main. That is a stacked branch, and git has had a good tool for keeping a stack in shape since Git 2.38 added git rebase --update-refs in October 2022.

Every command below runs in a throwaway repo. I build Hermes IDE, so weigh the parts about it with that in mind. The git half works without it.

Step 1: task A, then task B on top of it

Make a scratch repo with one file:

mkdir stack-demo && cd stack-demo
git init -b main
echo 'export const orders = [];' > api.js
git add api.js
git commit -m "Initial commit"
Enter fullscreen mode Exit fullscreen mode

Task A gets its own branch and folder through git worktree add. Play the agent and commit the API change:

git worktree add -b hermes/order-total ../order-total
cd ../order-total
cat >> api.js <<'EOF'
export function orderTotal(items) {
  return items.reduce((s, i) => s + i.price, 0);
}
EOF
git commit -am "Add orderTotal"
Enter fullscreen mode Exit fullscreen mode

Now start task B from task A's branch. The last argument to git worktree add is the start point. The docs put it plainly: -b creates the new branch "starting at ", and it defaults to HEAD when you leave it out. Pass task A's branch:

cd ../stack-demo
git worktree add -b hermes/show-total ../show-total \
  hermes/order-total
cd ../show-total
cat > ui.js <<'EOF'
import { orderTotal } from './api.js';
export const label = (items) =>
  `Total: ${orderTotal(items)}`;
EOF
git add ui.js
git commit -m "Show order total"
Enter fullscreen mode Exit fullscreen mode

This works even though hermes/order-total is checked out in the other folder. Git only refuses to check out the same branch in two worktrees, and here you are creating a new branch on top of it.

Two log ranges show the stack. git log --oneline main..hermes/show-total lists both commits. git log --oneline hermes/order-total..hermes/show-total lists only the UI commit, which is the part a reviewer of task B should read.

Two stacked blocks on a workbench, the lower one labelled by a gear and the upper one by a window, held together by an orange clamp
Task B sits on top of task A, not next to it.

In Hermes, ⌘N (Ctrl+Shift+N on Windows and Linux) starts each task on its own hermes/<name> branch in its own worktree, with the task as the agent's first prompt (README). The sheet's branch line shows what the new branch is cut from, "from main (current)" in the site's demo. Check that line before you start task B. If it doesn't show task A's branch, create task B's worktree with the git worktree add … hermes/order-total command above instead. Since 2.2.1, when a task starts from a branch other than the default one, the Review Desk shows only that task's own changes, the same thing the second git log range gave you.

The Hermes ⌘N New task sheet with a task typed in, chips for Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, model and effort, and the command preview
The ⌘N New task sheet in Hermes 2.3.4: agent, project, a new hermes/ worktree, approval mode, model and effort, and a preview of the command Hermes will run.

Step 2: task A changes after review

Review comes back on task A: the total ignores quantity. There are two ways the fix can arrive, and they need different commands on task B.

A new commit on top of A. The agent adds a commit and leaves the old one alone:

cd ../order-total
cat > api.js <<'EOF'
export const orders = [];
export function orderTotal(items) {
  return items.reduce((s, i) => s + i.price * i.qty, 0);
}
EOF
git commit -am "Multiply by quantity"
cd ../show-total
git rebase hermes/order-total
Enter fullscreen mode Exit fullscreen mode

The plain rebase is enough. Task A's original commit is already in hermes/order-total, so git replays only the UI commit on the new tip.

A rewritten A. This is the alternative to the block above. The reviewer wants one clean commit, so the fix is amended into the original. Starting again from the end of Step 1, save the old tip first, because task B is still built on it:

cd ../order-total
old=$(git rev-parse HEAD)
cat > api.js <<'EOF'
export const orders = [];
export function orderTotal(items) {
  return items.reduce((s, i) => s + i.price * i.qty, 0);
}
EOF
git commit -a --amend --no-edit
cd ../show-total
git rebase --onto hermes/order-total "$old"
Enter fullscreen mode Exit fullscreen mode

Without --onto, git would try to replay the pre-amend API commit on top of the amended one, and you would be resolving a conflict against your own code. --onto hermes/order-total "$old" means: take the commits after $old and put them on the new A. The rebase docs cover this as transplanting a branch.

Step 3: main moves, rebase the whole stack with --update-refs

Someone lands an unrelated change on main:

cd ../stack-demo
echo 'export const currency = "EUR";' > config.js
git add config.js
git commit -m "Add currency config"
Enter fullscreen mode Exit fullscreen mode

Both task branches are now behind. Rebasing them one at a time means remembering old tips again. --update-refs does it in one go: it will "automatically force-update any branches that point to commits that are being rebased". Rebase the top branch onto main and the branches inside the stack move with it.

There is one catch, and it bites exactly the setup worktrees give you. The same docs line goes on: "Any branches that are checked out in a worktree are not updated in this way." hermes/order-total is checked out in ../order-total, so a plain git rebase --update-refs main would move task B and leave task A's branch behind on the old main.

Detach task A's worktree for the length of the rebase, then put it back:

git -C ../order-total switch --detach
cd ../show-total
git rebase --update-refs main
git -C ../order-total switch hermes/order-total
Enter fullscreen mode Exit fullscreen mode

switch --detach with no commit stays on the same commit, so no file in ../order-total changes. Commit any work in that folder first anyway, because switching back moves it to the rebased branch. Then check the shape with git log --oneline --graph main hermes/show-total: the API commit sits on top of the currency commit, and the UI commit on top of that.

If you stack branches often, git config --global rebase.updateRefs true makes this the default (GitHub's 2.38 write-up). --no-update-refs turns it off for one rebase.

The Hermes Review Desk Worktrees tab listing one Hermes worktree each for orders-api, web-app and orders-types, with each worktree's hermes/ branch, task, age and disk size
The Review Desk's Worktrees tab in Hermes 2.3.4: one worktree per task, each with its hermes/ branch, task, age and disk size.

That tab is also the list of branches --update-refs will skip. A Hermes task started in a new worktree has its branch checked out there, so check this tab before a stack rebase and detach the lower task's folder.

Step 4: land them in order

Land A first, then B. After step 3 each branch sits directly on top of the one below it, so both merges are fast-forwards:

cd ../stack-demo
git merge --ff-only hermes/order-total
git merge --ff-only hermes/show-total
Enter fullscreen mode Exit fullscreen mode

--ff-only refuses rather than creating a merge commit, so if either one fails, the stack drifted and you rebase again before landing.

On GitHub, open task B's pull request against hermes/order-total, not main, so its diff shows only the UI change. When task A merges and its branch is deleted, GitHub retargets the dependent pull request to main instead of closing it. If task A was squash-merged, task B still carries A's original commits. Drop them while your local hermes/order-total still points at the pre-squash tip. Run it in task B's folder, since its branch is checked out there:

cd ../show-total
git fetch origin
git rebase --onto origin/main hermes/order-total
Enter fullscreen mode Exit fullscreen mode

In Hermes, the Land action commits, opens a pull request or merges locally in one step, and can be undone (README). The detail that matters for a stack is in the 2.0 changelog: "It lands into the branch the task started from and never over an edit you have not committed." If Hermes started task B from task A's branch, that branch is where B lands, not main. So landing B folds the UI change into hermes/order-total, and landing A afterwards takes both into main. If you would rather land A into main first and keep B separate, merge B by hand with the commands above. Either way, a Land you regret has an undo.

What this doesn't solve

Stacking fixes the order of work. It doesn't fix the work. If task A's review changes the function's signature, task B's code breaks after a clean rebase, and only your tests tell you. Run them in task B's folder after every rebase in steps 2 and 3.

Keep stacks short. Two or three branches are easy to hold in your head. Past that, every review round on the bottom branch means another rebase up the stack, and an agent working on the top branch is coding against an API that may still move.


If this was useful

Landing an API change before the caller that needs it is the same ordering problem you hit between services: a producer has to ship a field before any consumer reads it. My Event-Driven Architecture Pocket Guide covers that side, with sagas, the outbox pattern, and the contract traps that show up when changes land out of order. It is one of 8 short books in the Pocket Guides for Developers series, on agents, RAG, prompts, observability, events, databases and system design.

Hermes IDE is free to download, source-available under BSL 1.1, and runs on macOS, Windows and Linux; if it saves you a rebase, star it on GitHub.

Event-Driven Architecture Pocket Guide: Saga, CQRS, Outbox, and the Traps Nobody Warns You About

Top comments (0)