Skip to content

Branch Config

Branch detection and merge behavior, configured per-project in .claude/settings.local.json under the _branch key.

Script: ~/.claude/scripts/branch-detect.sh — single source of truth for branch resolution across all commands and hooks.

Field Type Default Description
_branch.main string|null auto-detect Override main branch name
_branch.session string|null auto-detect Override session/working branch
_branch.merge_strategy "ff-only" | "merge-commit" | "squash" "ff-only" Merge strategy for /finish-work and warp-drive
_branch.scope {"area": "<slug>"} | {"flightplan": "<N>"} | absent none Stream↔scope binding (#1177): the warp-drive scope bound to the session branch

When _branch is not set, branch-detect.sh auto-detects:

  1. Main branch: origin/HEAD → local main → local master → error
  2. Session branch: current branch if not on main, otherwise null
  3. Merge target: session branch if set, otherwise main
  4. Merge strategy: ff-only

Most repos need no configuration — detection just works.

{}

Output on a main-based repo while on feature/foo:

{"main":"main","session":"feature/foo","current":"feature/foo","on_main":false,"merge_target":"feature/foo","merge_strategy":"ff-only","scope":""}

Pin session branch so it persists even when on main (e.g., long-running dev branch):

{
"_branch": {
"session": "dev/sprint-3"
}
}

Bind a warp-drive scope (an area workstream or a flight-plan issue) to the integration branch when opening the stream, so every kickoff in the checkout lines up with it automatically:

Terminal window
warp session start dev/warp-stream --area warp-drive # or: --flightplan 1078
{
"_branch": {
"session": "dev/warp-stream",
"scope": { "area": "warp-drive" }
}
}

The binding is enforced at /warp-drive kickoff by resolveScopeBinding() (scripts/warp-drive/kickoff.js), feeding the run’s normal session.kickoff state so scope precedence stays in kickoffScope() (#992):

  • No scope flags → the kickoff defaults to the bound scope; an unscoped run on a bound stream can never reach the full approved queue.
  • Explicit matching scope → passes through unchanged.
  • Explicit --issue → allowed; a named issue is explicit operator intent strictly narrower than the binding.
  • Different area/plan (or mixed kinds) → refused with a message naming the bound stream and how to re-bind or end it.

Because it lives in settings.local.json, the binding survives session end, segmentation, and machine restart — and it is per-checkout, so cdfork worktrees each carry their own binding and parallel streams on different areas coexist. warp session end and warp finalize clear it together with the branch pin; to run a different scope, re-bind (warp session start <branch> --area <slug>) or end the stream first. A scope recorded without a session pin is inert.

branch-detect.sh reports the binding as a scope field ("area:<slug>" / "flightplan:<N>", empty when unbound; also via --field scope); warp session status and warp status display it omit-when-empty.

For repos using develop as the integration branch:

{
"_branch": {
"main": "develop"
}
}

For projects that prefer squash merges:

{
"_branch": {
"merge_strategy": "squash"
}
}
Strategy Git flag Behavior
ff-only --ff-only Fast-forward only, fails if diverged
merge-commit --no-ff Always create a merge commit
squash --squash Squash all commits into one

Unified freshness/finalize policy (#1178/#1179)

Section titled “Unified freshness/finalize policy (#1178/#1179)”

Integration-branch streams follow one sync policy in both directions, and it never rebases:

  • During the stream (kickoff, segment resume, warp session start on an existing branch): main is merged into the stream by scripts/warp-drive/stream-freshness.sh. A pushed stream must never be rebased — rewriting shared history breaks every checkout tracking it.
  • At warp finalize (L3): the stream is freshened one last time (same merge-in engine — no rebase-onto-main step), then shipped to main using the configured merge_strategy flag above. Because a freshened stream strictly descends from main, the ff-only default ships cleanly with any absorbed sync-merge commits preserved in history; set merge-commit if you want an explicit --no-ff ship commit marking the stream boundary. The policy is carried entirely by merge_strategy — there is no hidden finalize-specific override.

When does rebase apply? Never inside warp tooling. Rebasing is acceptable only as a manual choice on a branch that has never been pushed (nothing shared to rewrite); once a stream has an upstream, merge-in is the only sync direction.

Command / Script Uses
/start-work Detects main for checkout; writes _branch.session after branch creation
warp session start/end, warp finalize Write/clear _branch.session + _branch.scope (stream↔scope binding, #1177)
kickoff.js (resolveScopeBinding) Applies _branch.scope at /warp-drive kickoff — bound default, conflict refusal
/finish-work Detects main for rebase/merge target
/warp-drive Detects main, session, merge_target, merge_strategy for all merge operations
check-branch.sh hook Detects main for branch enforcement
state-machine.js Stores branch fields in warp-drive state