Sync, verify, merge
A session’s work lives on its own spwn/<id> branch until you bring it back. spwn shows
you what the merge will do before you run it. It lets you pull the base into the session
first, so any conflict lands with the agent that wrote the code instead of with you. And
it can build and test the merged result before anything reaches your base branch.
To merge many sessions in turn without driving each one, use the land order. It runs these same steps for you.
Open the Merge dialog
Section titled “Open the Merge dialog”- Merge… on the status strip at the top of a session.
- ⤵ Merge… in the Inspector’s Overview tab. It’s enabled once the branch has commits its base doesn’t.
The dialog shows spwn/3f9c2a1b → main and, before you do anything:
- N commits ahead and N files changed, plus N behind when the base has moved on;
- N conflicts, or merges cleanly;
- the changed files, with conflicting ones marked !.
Then choose:
- Commit this session’s uncommitted changes first: on by default. Turn it off and only committed work merges; the rest stays in the worktree.
- Delete this session after merging: off by default. It turns Merge into Merge & delete, which removes the worktree and branch once the merge succeeds.
- Merge: merges now.
- Add to land order: hands the session to the land order instead.
The Inspector’s ↩ Bring work back… is the lighter route. It offers Merge into base (which always commits leftovers first), Send to another session (pastes the last reply into another session’s composer), and Add to the Merge tray.
What a merge does
Section titled “What a merge does”spwn merges in the checkout that has the base branch checked out, which is usually your project folder:
- If the base is already in the session’s branch, it’s a fast-forward:
git merge --ff-only spwn/<id>. - Otherwise it’s an ordinary three-way merge:
git merge --no-edit spwn/<id>.
There’s no squash and no rebase. The commits land as they are. The merge uses your git
identity, or spwn session <spwn@localhost> if the repo has none.
If git stops on a conflict, spwn runs git merge --abort, so your base is left exactly
as it was, and tells you. When the preview already shows conflicts, the button reads
Merge anyway. Sync first instead.
If the base isn’t checked out anywhere, spwn asks you to check it out, for example in your project folder, before it will merge into it.
Plain git works too. It’s your repo and your branch:
git merge spwn/3f9c2a1bThe conflict preview
Section titled “The conflict preview”spwn works out conflicts with git merge-tree --write-tree. This runs entirely in memory:
no checkout is touched and no ref moves, so it’s cheap enough to run every time the status
refreshes. It needs git 2.38 or newer. If it can’t run, the dialog says Couldn’t check
for conflicts ahead of time instead of claiming the merge is clean.
The same answer shows up in other places:
- collides with main on the session’s status strip;
- ⚠N on its sidebar and Fleet rows;
- ⚠ conflicts with main in N in a Compare lane.
The preview lists conflicting files, not hunks.
Sync from base
Section titled “Sync from base”When the base has moved on, Sync with main brings it into the session:
- spwn commits any uncommitted work on the session branch first, as
spwn: uncommitted work, committed before syncing with base; - it runs
git merge --no-edit maininside the session’s own worktree.
Your base checkout is never touched. Once the sync is clean, the later merge into the base is a fast-forward, which can’t conflict.
Sync waits for a running turn to finish, so it never takes a half-written tree.
A conflicted sync goes to the agent
Section titled “A conflicted sync goes to the agent”If the sync stops on conflicts, spwn leaves the merge open in the session’s worktree, with the conflict markers in the files, and puts a note in that session’s composer:
I merged `main` into this session's branch and it stopped on conflicts.
Conflicted files:- src/api/client.ts- src/api/retry.ts
The merge is still open in this worktree. Please resolve each conflict — you havethe context for our side of it, so keep both intents where they do not actuallydisagree — then stage the files and commit the merge.
Do not run `git merge --abort`: that would throw the sync away.The agent that wrote this side of the code is the one best placed to reconcile it. The note is pasted, not sent, so you press Enter when you’re ready. (The land order sends it for you. See Hand-offs.)
While the sync is open, the status strip shows sync unresolved and merging is blocked. The dialog offers two buttons:
- Hand to the agent pastes the note again.
- Abort sync runs
git merge --abortand puts the branch back where it was.
With rerere on, a conflict someone already resolved in another session is replayed automatically. If that leaves no markers, spwn commits the merge itself.
Verify the merged result
Section titled “Verify the merged result”Two branches can each pass their tests and still break when they meet. Verify merged result builds the combination and tests it before it lands.
- spwn makes the merged tree as an unreferenced commit. No branch moves.
- It checks that commit out into a throwaway worktree at
<repo>/.spwn/trial/<session>, excluded from git. - It clones the heavy build folders (
node_modules,targetand the rest) in from the session’s worktree. - It runs every
session-integratehook there. - It deletes the throwaway worktree.
Verify is offered once there’s something to merge and no conflicts. There’s nothing merged to test until conflicts are resolved.
Write the check as an ordinary hook:
# ~/.spwn/hooks/session-integrate.d/10-test.sh (every project), or# .spwn/hooks/session-integrate.d/10-test.sh (committed with this repo)set -enpm run buildnpm testIt runs with the trial worktree as its working directory. Besides the usual SPWN_*
variables it gets:
SPWN_TRIAL_WORKTREE: the merged tree;SPWN_SESSION_WORKTREE: the session’s own worktree.
Repo-scoped scripts are read from the merged tree itself. See How hooks work.
The result reads The merged result passed N checks, or names the scripts that
failed. With no session-integrate scripts it says nothing was checked, and it merges.
Stale checks
Section titled “Stale checks”A check tests one specific pairing of the session and the base. spwn records the base commit it verified against. If the base moves afterwards, the result is marked stale: The base has moved since this check — it tested a different combination than would land now. Compare lanes show (stale) for the same reason.
Verify never blocks a manual merge. The land order can require it. See
requireVerify.
Collisions and overlaps
Section titled “Collisions and overlaps”spwn watches for two different kinds of clash.
The base has moved into conflict with a session (a collision). Each time the session’s status is checked, spwn runs the conflict preview. When a conflict appears, spwn puts an early warning in that session’s composer:
Heads up: `main` has moved, and it now conflicts with work in this session.
Conflicting files:- src/api/client.ts
Nothing is broken yet — this is just early warning. Reconciling now is smallerthan reconciling later, and you have the context for our side of it.
When you reach a sensible stopping point: `git merge main` in this worktree,resolve anything it stops on, and commit the merge.
If you would rather keep going, that is fine too — just expect more to reconcile.Like every note spwn gives an agent, it’s pasted, not sent. The agent reads it when you next send. It’s announced once for each set of conflicting files, and a new or different set is announced again. It’s delivered to a session that is open in a browser tab.
Two sessions change the same files (an overlap). spwn compares each session’s changed files against its own base, so a fork doesn’t overlap its parent over work it inherited. Overlaps are shown to you and not pushed to the agent:
- N sessions in the same files on the status strip;
- ◑N on sidebar and Fleet rows, and ◑ title: files in Compare;
- Also being edited elsewhere in the Merge dialog.
An overlap isn’t a problem by itself. It tells you where two sets of changes will have to meet.
Both checks work per file, not per hunk. Neither can be turned off.
Your uncommitted edits are safe
Section titled “Your uncommitted edits are safe”The base checkout is usually your project folder, and you may be working in it. spwn doesn’t make you stash so an agent can land.
- A dirty base checkout is fine. git merges around your edits when the incoming changes don’t touch them.
- If the session changes a file you have uncommitted edits in, including untracked files, spwn won’t merge. It says: Not landing yet — that would overwrite work in progress in src/app.ts. The session’s work stays on its branch.
- The status strip shows you’re editing N of these files, and the Merge dialog names them.
- In the land order the entry waits instead of failing. It lands once you commit those files, and the sessions behind it keep landing meanwhile.
The per-turn commit and the sync only ever run in the session’s worktree. After a merge,
spwn makes one commit of its own in your checkout, spwn: prune N merged PR doc(s), which
removes the merged session’s PR doc. It stages only those
doc files, never your work in progress.
Landings into the same checkout take turns, so two sessions finishing at once land one
after the other instead of tripping over git’s index.lock.