---
name: finishing-a-development-branch
description: Use when development is complete and verified — submits the feature branch to the standing land queue (merge-to-main or open-a-PR, per project); the conductor gates, lands, deploys, and cleans up the worktree. Resolves conflicts by agent judgment; NEVER prompts the user.
---

# Finishing a Development Branch

audience: AI coding agents first. Imperative, BLUF.

MUST follow `/home/user/Projects/0 DOCS/GIT_FATIGUE.md` §12. The standing conductor owns gating,
merge/push, deploy, and cleanup. Agents MUST NOT run raw publication commands, wait for a landing
result, or surface source-control/CI choices to the user.

Submit a verified branch by driving the TESTED shared brain — never by hand-typing git, never by
waiting. The agent resolves every conflict by judgment (the rubric) if and when one comes back to
it; the user is NEVER involved in git. Land mode (merge-to-main vs open-a-PR) is FROZEN per
project, not decided here.

## Submit it — one command, exits in under a second, your job is done

```bash
bash <PROJECT_ROOT>/.claude/scripts/ship.sh submit <branch> <worktree>
```

This enqueues the branch and returns — accepted, not yours anymore. The standing conductor
(no agent, no session) gates → merges/opens-PR → pushes → deploys → removes the worktree and
branch, on its own schedule. Emit only `Submitted <ticket-id>.` and stop. Proceed to
`learn-from-mistakes`.

**NEVER poll `submit-status` in a loop waiting for the result** — that re-creates the exact wait
`submit` exists to remove. `submit-status <ticket>` is a read-only one-off spot-check (e.g. the
owner asks "did it land yet"), never a wait loop, and it never conducts, prunes, or takes a lock.

### Read the exit code

- **exit 0** → submitted (or already-submitted, same ticket, no duplicate). Ticket id in stdout
  JSON. Stop here.
- **exit 20** → the source worktree itself fails the same conflict-markers/dirty-tree guard `land`
  uses. Fix in the worktree, re-submit.
- **exit 3** → usage/environment fault: bad args, cwd inside the worktree, stage-a queue not
  enabled for this repo, unsupported mode, or drift. Fix, re-submit.

## If a ticket comes back needing agent action

A ticket the conductor could not push (conflict, failing gate, tests) is left queued with a
non-`pushed` verdict; its worktree and branch are NOT cleaned up (cleanup only follows a genuine
push). Whoever picks the ticket back up — reads `/requests` or runs `submit-status <ticket>` —
resolves it in the SAME worktree per the rubric below, then re-submits the same branch/worktree.

## Resolving conflicts — the agent decides, never the user

Follow `~/.claude/workflows/lib/merge-conflict-rubric.md` EXACTLY. Core: integrate both by default;
delete a side only after proving it carries zero unique behavior; on accidental double-work the
better version wins (tests/completeness, NOT "newer"); green tests over uncovered lines → say
"resolved but unverified at `<file>:<line>`".

- **NEVER** blanket `git merge -X theirs` or `git checkout --theirs -- <file>` to clear a conflict —
  the silent-loss move the rubric exists to kill.
- **NEVER** escalate a conflict to the user.

## No project wrapper yet? GENERATE one (the right way)

Missing `<PROJECT_ROOT>/.claude/scripts/ship.sh` is executable setup, NEVER an external blocker.
Run the generator; it sniffs project facts and freezes them into the wrapper. NEVER ask owner or
hand-type publication cascade. Two phases (fail-closed on land-mode call):

```bash
bash ~/.claude/workflows/lib/ship-init.sh <PROJECT_ROOT>                 # Phase A: PROPOSE (exit 10), writes nothing
bash ~/.claude/workflows/lib/ship-init.sh <PROJECT_ROOT> --mode pr|merge-to-main [--testcmd "…"]   # Phase B: emit wrapper
```

Phase A prints proposed internal mode + evidence. Resolve internally; NEVER surface PR/merge/push choice to owner.
Pass `--testcmd` only when no valid current-tree receipt exists and project controller requires a canonical gate command.
Phase B writes the frozen wrapper + a smoke test + a local exclude (when `.claude` is tracked). Then
submit via the wrapper above.

One-off (no wrapper wanted) → drive the orchestrator directly (same submit contract):
```bash
bash ~/.claude/workflows/lib/finish-branch.sh submit --root R --base B --mode M --anchor KIND:VAL --testcmd T [--depcmd D] -- <branch> <wt>
```

## Legacy: blocking `land` — DO NOT USE for new work

`ship.sh land <branch> <worktree>` / `finish-branch.sh land …` still exist and still work today,
but they run gate→land→cleanup IN the calling agent's own process — the agent holds context,
tokens, and a session open until a landing finishes, exactly what `submit` exists to remove. Every
new brief targets `submit`. `land` is kept only for one-off manual/debug invocation outside an
agent session; once the daemon-only landing-queue flag is live, `land` refuses and names `submit`
as the replacement — treat any surviving call site in agent-facing docs or scripts as a bug to
fix, not a pattern to copy.

## Red Flags — NEVER

- Hand-type the merge/push/worktree cascade instead of running `submit`.
- Wait or poll for a submitted ticket's landing result instead of stopping at "submitted".
- Call the legacy blocking `land` path from new agent work.
- Blind `-X theirs` / whole-file `--theirs` (see rubric).
- Escalate a conflict to the user.
- Force-push main (no history rewrite on a shared branch).
- Land with failing tests — the `land-merge` gate enforces this; do not bypass it.

## Integration

**Called by:** `ship` — final step, replaces the old push/deploy + merge tail.
**Pairs with:** `using-git-worktrees` — the conductor cleans up the worktree it created, once landed.

## Learned rules — now enforced mechanically by `finish-branch.sh`

These hard-won footguns are why the cascade is now tested code, not prose. Each is enforced by a
named primitive; kept here as the WHY so no future editor re-inlines raw git. They apply to both
the standing conductor's own conduct pass and the legacy `land` path — same primitives underneath.

- **worktree-remove-before-branch-delete** → `cleanup` orders push → worktree-remove --force →
  branch -d. (`branch -d` before worktree-remove fails "used by worktree".)
- **never-delete-wip-refs** → cleanup automation never deletes `origin` `wip/*` refs (salvaged WIP
  namespace) — local `branch -d` only, remote wip stays.
- **merge-from-worktree-cwd / merge-from-main-not-worktree** → every primitive uses `git -C <dir>`;
  merges target the right repo, never "Already up to date" from a wrong cwd.
- **dirty-src-before-merge / wip-feature-work-blocks-merge** → `preflight` reports a dirty main
  worktree (src/ contamination AND coherent WIP) before any land; the conductor/agent clears it.
- **pending-merge-on-main-blocks-checkout** → `preflight` detects MERGE_HEAD / unmerged paths and
  blocks until resolved or aborted.
- **worktree-remove-destroys-gitignored-assets** → `cleanup` refuses to remove a worktree holding
  non-empty `tmp/.cache/.turbo/dist/coverage` until preserved (`--assets-ok`).
- **merge-theirs-drops-imports / merge-conflict-resolution-missing-vars** → the blind `-X theirs`
  path is DELETED; conflicts go to the rubric (read both sides, integrate, verify with tests).
