Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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” — start refuses 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 main while one or more agents are running on feat/* 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 rebase exits zero with no rewrite when the branch is already up to date.
  • The branch ref is updated even in the resume case — git paw start on a session whose worktrees already exist on disk picks up the new main commits transparently.

Rebases that don’t happen:

  • A brand-new branch (one git-paw creates via git worktree add -b during 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:

  1. A local main branch, if one exists.
  2. Otherwise a local master branch, if one exists.
  3. 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:

  1. Runs git rebase --abort on the affected branch.
  2. Leaves the branch at its pre-rebase HEAD (no half-rebased state survives).
  3. Exits with an error like Error: rebase onto main failed: <git's stderr listing the conflicting files>.

When you see this error:

  1. cd into the affected agent’s worktree (by default at .git-paw/worktrees/<branch-slug>/; ../<project>-<branch-slug>/ with worktree_placement = "sibling").
  2. Inspect the conflicting files git named in the error.
  3. Either reconcile manually (e.g. git rebase main followed by resolving conflicts and git rebase --continue), or decide the change should land via a merge commit instead.
  4. Once the branch is in a clean state, rerun git paw start (with or without --no-rebase depending 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 fetch or git pull for main. The rebase target is whatever origin/HEAD tracks 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 start returns 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:

  1. 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 as git paw start --specs NAME). An unknown spec name errors with the discovered candidate list; an unknown --cli errors with the detected CLI ids — and in both cases nothing is created.
  2. Refuses — before touching anything — a branch whose broker agent_id is already taken by an agent in the session. The id is the slugified branch name, so two different branches can map to one id: adding feat-x to a session already running feat/x fails naming both branches and the shared id feat-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.)
  3. Enforces the 25-agent cap before touching anything. Adding the 26th agent fails with the same “split into multiple sessions” message start uses.
  4. 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.
  5. Re-tiles the agent grid to the layout a start of that many agents would have produced. Existing panes keep their indices, so any in-flight send-keys targeting (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.
  6. Registers the branch in the session so subsequent status, stop, purge, and pause include 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:

  1. Locates the agent by branch (errors with the live-agent list if it isn’t in the session).

  2. Uncommitted-work safety: refuses to delete a worktree with uncommitted changes, listing the changed files, unless you pass --force. This mirrors git worktree remove’s own safety. --keep-worktree skips the check entirely (nothing is deleted).

  3. 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_path and 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 from pane_current_path on the supervisor’s next sweep, so a mid-grid removal is safe.

  4. Removes the worktree (reusing git paw purge’s per-worktree teardown) unless --keep-worktree, then drops the branch from the session.

  5. 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/wip
    

    The discard command is git branch -d when git would accept the safe delete, -D when only the forced form would work.

    --force relaxes 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 / remove operate 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). add and remove refuse 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 layout

Recovery rebuilds the window as a grid and records it, so after that restart add and remove work on the session normally. Sessions started by this version are already grids and are never refused.