Appearance
Branch Configuration (_branch)
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.
Schema
| 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 |
Auto-Detection (no config)
When _branch is not set, branch-detect.sh auto-detects:
- Main branch:
origin/HEAD→ localmain→ localmaster→ error - Session branch: current branch if not on main, otherwise null
- Merge target: session branch if set, otherwise main
- Merge strategy:
ff-only
Examples
Default (no config needed)
Most repos need no configuration — detection just works.
json
{}Output on a main-based repo while on feature/foo:
json
{"main":"main","session":"feature/foo","current":"feature/foo","on_main":false,"merge_target":"feature/foo","merge_strategy":"ff-only","scope":""}Explicit session branch
Pin session branch so it persists even when on main (e.g., long-running dev branch):
json
{
"_branch": {
"session": "dev/sprint-3"
}
}Stream↔scope binding (#1177)
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:
bash
warp session start dev/warp-stream --area warp-drive # or: --flightplan 1078json
{
"_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.
Custom main branch (develop)
For repos using develop as the integration branch:
json
{
"_branch": {
"main": "develop"
}
}Squash merging
For projects that prefer squash merges:
json
{
"_branch": {
"merge_strategy": "squash"
}
}Merge Strategy → Git Flag Mapping
| 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)
Integration-branch streams follow one sync policy in both directions, and it never rebases:
- During the stream (kickoff, segment resume,
warp session starton an existing branch): main is merged into the stream byscripts/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 configuredmerge_strategyflag above. Because a freshened stream strictly descends from main, theff-onlydefault ships cleanly with any absorbed sync-merge commits preserved in history; setmerge-commitif you want an explicit--no-ffship commit marking the stream boundary. The policy is carried entirely bymerge_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.
Consumer Commands
| 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 |