Session Lifecycle
This chapter covers what git paw start does to your worktrees on each
launch (or relaunch), and how to control its behaviour when the defaults
don’t fit.
Reattaching instead of forking
Before creating anything, git paw start looks up whether the current
repository already has a live session (matched by repository path, not by
project/session name) and reattaches to it instead of launching a fresh
one. This applies uniformly whether the session is a bare launch or a
supervisor-mode one ([supervisor] enabled = true in config, or
--supervisor) — a supervisor-enabled repo’s bare git paw start still
goes through the same guard before it ever reaches the supervisor
auto-start flow.
- Interactively, it attaches your terminal to the existing session —
identical to running
git paw attach. - Non-interactively (no TTY — a script, a cron job, an automated
scanner), there is nothing to attach to. Rather than silently printing a
hint and exiting successfully — which an automated caller could mistake
for “a fresh session was launched” —
startrefuses with an actionable error naming the live session and how to attach to it.
The -N suffix (paw-<project>-2, -3, …) is reserved for genuinely
distinct repositories that happen to share a project name (their
directory basename); it is never applied as a result of re-running start
against a repository that already has a session running.
Rebase on start
Every git paw start rebases each existing agent branch onto the
repository’s default branch (whatever origin/HEAD tracks — typically
main) before opening or reopening that branch’s worktree. This
keeps agents starting from current main, which matters when the
supervisor (you) advances main while agents are still working on their
branches.
Why this is the default:
- A supervisor session typically lands commits on
mainwhile one or more agents are running onfeat/*branches. Without rebasing on start, every subsequent agent commit chains from the stale baseline, forcing a per-branch rebase at merge time. - The rebase is cheap when there’s nothing to do:
git rebaseexits zero with no rewrite when the branch is already up to date. - The branch ref is updated even in the resume case —
git paw starton a session whose worktrees already exist on disk picks up the newmaincommits transparently.
Rebases that don’t happen:
- A brand-new branch (one git-paw creates via
git worktree add -bduring this launch) is not rebased. It’s already at the current default-branch tip by construction.
Resolving the default branch without origin/HEAD
The rebase target is resolved by a single resolver shared by every launch
path (start, add, and the rebase step itself), so start and a later
resume can never disagree on which branch is “the default”. It prefers
refs/remotes/origin/HEAD (whatever your remote’s default branch is —
typically main); when that isn’t set (a repository with local-only
commits and no remote, or a remote whose HEAD was never configured), it
falls back deterministically instead of aborting:
- A local
mainbranch, if one exists. - Otherwise a local
masterbranch, if one exists. - Otherwise the currently checked-out branch.
This means a fresh launch and every subsequent resume resolve to the same
default branch and neither aborts for a reason the other didn’t hit —
push the default branch to origin and run git remote set-head origin <branch> if you’d rather pin it explicitly than rely on the fallback.
Opting out: --no-rebase
Pass --no-rebase when you want the pre-v0.6 behaviour — agent branches
opened at their current SHA with no rebase attempt:
git paw start --no-rebase
git paw start --no-rebase --cli claude --branches feat/auth,feat/api
git paw start --no-rebase --supervisor # combines with other flags
Use cases:
- You are deliberately working against a stale baseline (e.g. an agent is reproducing a bug on a specific historical commit).
- An external tool has pinned a pre-rebase SHA on the agent branch and rewriting history would break it.
- You’re scripting against the pre-v0.6 launch contract and don’t want the new default behaviour.
Rebase conflicts
If the rebase hits a conflict — typically because both main and the
agent branch modified the same line of the same file — git paw start:
- Runs
git rebase --aborton the affected branch. - Leaves the branch at its pre-rebase HEAD (no half-rebased state survives).
- Exits with an error like
Error: rebase onto main failed: <git's stderr listing the conflicting files>.
When you see this error:
cdinto the affected agent’s worktree (by default at.git-paw/worktrees/<branch-slug>/;../<project>-<branch-slug>/withworktree_placement = "sibling").- Inspect the conflicting files git named in the error.
- Either reconcile manually (e.g.
git rebase mainfollowed by resolving conflicts andgit rebase --continue), or decide the change should land via a merge commit instead. - Once the branch is in a clean state, rerun
git paw start(with or without--no-rebasedepending on what you decided).
The conflict-abort path is intentional and not configurable — leaving a branch half-rebased poisons every subsequent git operation against it, which is far worse for the agent in that pane than a clean error at launch time.
What rebase does NOT do
- It does not
git fetchorgit pullformain. The rebase target is whateverorigin/HEADtracks locally; the user owns the remote-sync decision. - It does not leave a surviving worktree’s files behind its branch. The rebase runs inside the worktree that has the branch checked out, so on resume that worktree’s files move with the rebased HEAD. If git refuses the rebase there (for example, the worktree has uncommitted changes), it fails like a conflict — see above.
- It does not rebase one agent branch onto another agent branch. Only
feat/<change>onto the repository’s default branch. - It does not loop or retry on failure. One attempt, abort on conflict, surface the error.
Resume vs. fresh launch
The rebase step runs in both flows:
- Fresh launch: branch exists locally, no worktree yet. Branch is
rebased onto
main, then the worktree is created at the rebased SHA. - Resume (worktree already on disk from a prior session): branch
is rebased in the existing worktree, the existence check confirms
the worktree is registered for that branch, and
git paw startreturns success with the worktree’s branch ref now pointing at the rebased HEAD.
If you’re debugging a launch and want to know whether the rebase
modified anything, git rev-parse <branch> from the main repo before
and after git paw start will show the SHA change (or lack thereof).
The broker host
When the broker is enabled, every launch path — git paw start, the
spec-driven launch, the supervised launch, cold recovery and resume from
pause — runs the broker in its own process, git paw __broker, in a
detached tmux window named broker. The window sits outside the pane grid,
so pane numbering is unchanged; switch to it to see the host’s output.
A launch opens the session, starts the host, and waits up to 10 seconds for it to answer before it registers the agents with it, saves the session, or sends any boot prompt. If the host exits or never answers — most often because another process holds the broker port — the launch fails with an error naming the port and saves no session, so nothing half-launched is recorded. The dashboard in pane 0 is a client of the host: it shows ⚠ broker unreachable while the host is down and reconnects on its own.
git paw pause closes the broker window (the dashboard stays);
git paw stop ends the whole session, host included. Both flush the
session’s broker log on the way out.
Cold recovery resumes worker context
When git paw start recovers a stopped session — including a paused
session whose tmux server died, and a session still up in tmux whose
every agent pane has dropped back to a bare shell (crashed CLIs) — a
CLI that supports it is relaunched with its prior conversation context
restored instead of starting cold. See Per-CLI cold resume
for the mechanism (session-id assignment at launch, resume on
recovery) and which CLIs it covers today.
A session whose panes dropped back to a bare shell reports 🟡 stopped
with Tmux: running, never 🔴 stale — its tmux session is still there,
so git paw purge --stale leaves it alone and start is the remedy.
A receipt that still claims active after its tmux session vanished
(a crash, or a machine sleep that tore the tmux server down) is not
recovered: git paw status reports it as 🔴 stale, and start
clears it the way git paw purge --stale does, then launches fresh.
Adding and removing branches mid-session
Before v0.6.0 the branch set was frozen at git paw start time: changing
it meant stop/purge + re-start, which destroyed every agent’s
in-flight conversation. git paw add and git paw remove edit a running
supervisor session’s branch set in place — the other agents keep working
undisturbed.
git paw add — hot-attach an agent
git paw add feat/new-thing # attach a worktree + pane, session's default CLI
git paw add feat/api --cli codex # choose the CLI for the new pane
git paw add --from-spec add-export # derive branch + CLI from a discovered spec
What it does:
- Resolves the branch and CLI — from the positional argument, or (with
--from-spec) from a spec discovered in the configured[specs]format — OpenSpec, Markdown, Spec Kit, or Superpowers (same resolution asgit paw start --specs NAME). An unknown spec name errors with the discovered candidate list; an unknown--clierrors with the detected CLI ids — and in both cases nothing is created. - Refuses — before touching anything — a branch whose broker
agent_idis already taken by an agent in the session. The id is the slugified branch name, so two different branches can map to one id: addingfeat-xto a session already runningfeat/xfails naming both branches and the shared idfeat-x, because the two agents would share one inbox. Rename the branch so the ids differ. (Only when the broker is enabled; with it off, nothing addresses agents by id.) - Enforces the 25-agent cap before touching anything. Adding the 26th
agent fails with the same “split into multiple sessions” message
startuses. - Creates the worktree (same naming and idempotent-create behaviour as
start; re-adding an existing worktree reuses it) and attaches the agent through the same boot pipeline a start-time agent uses — the broker boot block, the sidecar spec body (.git-paw/AGENTS.local.md), and the initial prompt are byte-identical. - Re-tiles the agent grid to the layout a
startof that many agents would have produced. Existing panes keep their indices, so any in-flightsend-keystargeting (supervisor sweeps) still lands on the right pane; the new pane gets the next index. The re-tile preserves every other agent’s pane — no existing agent loses its pane — and each agent row is rebalanced to equal width, so the added grid matches a start-time grid of the same count in both pane count and pane widths. After the re-tile, git-paw reconciles the session JSON against the live panes and warns if any agent has no live pane (a JSON↔tmux desync), so it is visible and recoverable rather than silent. - Registers the branch in the session so subsequent
status,stop,purge, andpauseinclude it.
The new agent’s boot block is injected only once its CLI reaches an
interactive ready state (the launch-readiness gate), not after a blind
fixed sleep — the same protection git paw start applies.
The supervisor is not signalled directly. The new agent auto-registers with the broker (filesystem watcher + its own boot-block heartbeat) and the supervisor discovers it on its next sweep — no restart, at most one sweep-interval of latency.
Paused sessions: if the session is paused when you add, the new
pane is created but its prompt is held unsubmitted — the agent stays
paused with the rest of the session and begins on the next
git paw start.
git paw remove — detach a single agent
git paw remove feat/done-thing # close pane, remove worktree, drop from session
git paw remove feat/wip --force # remove even with uncommitted changes
git paw remove feat/keep --keep-worktree # detach pane only; leave worktree + branch on disk
What it does:
-
Locates the agent by branch (errors with the live-agent list if it isn’t in the session).
-
Uncommitted-work safety: refuses to delete a worktree with uncommitted changes, listing the changed files, unless you pass
--force. This mirrorsgit worktree remove’s own safety.--keep-worktreeskips the check entirely (nothing is deleted). -
Kills the agent’s pane and re-tiles the grid for the smaller agent count so it re-flows without leaving a hole. The target pane is resolved by mapping the removed branch’s worktree to a live pane via
pane_current_pathand killed by its tmux pane id — regardless of the process running in it (a bare shell from a failed/never-started CLI, a CLI, or anything else). Killing by resolved pane id (not by a position computed from the session JSON) guarantees only the removed agent’s pane is closed and never a different agent’s pane, even if a stale orphan pane has shifted the grid. The re-tile preserves every other agent’s pane and rebalances each row to equal width. The branch→pane mapping for the survivors is re-derived frompane_current_pathon the supervisor’s next sweep, so a mid-grid removal is safe. -
Removes the worktree (reusing
git paw purge’s per-worktree teardown) unless--keep-worktree, then drops the branch from the session. -
Branch safety: deletes the branch only when git-paw created it and its tip is an ancestor of the default branch or of the branch it was created from, printing
Deleted branch <name> (was <sha>). A branch that existed before the session is never deleted. A created branch carrying commits in neither — or one whose merge state cannot be determined — is kept after its worktree is gone, and is reported with its tip and how to pick the work back up or throw it away:Kept branch feat/wip at 9f8e7d6 (3 commit(s) not in main) resume it with: git worktree add '/path/to/worktree' feat/wip discard it with: git branch -D feat/wipThe discard command is
git branch -dwhen git would accept the safe delete,-Dwhen only the forced form would work.--forcerelaxes the uncommitted-work check only; it never deletes a branch with unmerged commits.
git paw remove supervisor is refused — to end the whole session use
git paw stop (or git paw purge). The supervisor notices a removed
agent passively: its heartbeat stops, and the supervisor drops it from
its coordination scope on the next /status poll.
Scope:
add/removeoperate on every live session — bare, spec-driven and supervisor-mode alike. Every session git-paw builds uses the agent grid, which is the layout that can be re-tiled incrementally for a new agent count, so dynamic branch attachment no longer depends on the session’s mode. One branch per invocation; bulk add/remove and configurable layouts are tracked for a later release.One exception — a session started by an earlier git-paw. Bare sessions used to be laid out with tmux’s whole-window tiled layout, which cannot be re-tiled while the session runs (re-tiling it would move every running pane under you).
addandremoverefuse such a session before touching anything, with an error naming the tiled layout. The fix is one restart:git paw stop # or detach and let the session go stale git paw start # rebuilds the window in the agent grid layoutRecovery rebuilds the window as a grid and records it, so after that restart
addandremovework on the session normally. Sessions started by this version are already grids and are never refused.