Skip to content

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.

  • 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.

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:

Terminal window
git merge spwn/3f9c2a1b

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.

When the base has moved on, Sync with main brings it into the session:

  1. spwn commits any uncommitted work on the session branch first, as spwn: uncommitted work, committed before syncing with base;
  2. it runs git merge --no-edit main inside 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.

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 have
the context for our side of it, so keep both intents where they do not actually
disagree — 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 --abort and 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.

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.

  1. spwn makes the merged tree as an unreferenced commit. No branch moves.
  2. It checks that commit out into a throwaway worktree at <repo>/.spwn/trial/<session>, excluded from git.
  3. It clones the heavy build folders (node_modules, target and the rest) in from the session’s worktree.
  4. It runs every session-integrate hook there.
  5. 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:

Terminal window
# ~/.spwn/hooks/session-integrate.d/10-test.sh (every project), or
# .spwn/hooks/session-integrate.d/10-test.sh (committed with this repo)
set -e
npm run build
npm test

It 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.

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.

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 smaller
than 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.

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.