Base-branch picker
How the Base branch picker in the Start session dialog works: choosing which git branch a new session's worktree forks from and merges back into, where the dropdown appears, how the choice is remembered on the session, and the one boundary worth knowing about the auto-lander.
What it is
One line: When you start a new session, you can choose which git branch its worktree forks from — and that same branch is where the session's work merges back when it's done — instead of always branching off (and merging into) master.
What it does (user-visible)
Omniscio isolates most sessions in their own git worktree (a separate working copy
on its own branch) so agents don't step on each other. Until now that worktree always
started from the repo's current HEAD (normally master), and the session's work
merged back into master when the session ended.
The base-branch picker adds one control to the Start session dialog: a Base
branch dropdown. Pick a branch (say develop or a feature branch) and:
- the new session's worktree is forked from that branch (it starts with that branch's code), and
- when the session finishes and its work merges back, it merges into that same branch, not master.
Pick nothing and everything behaves exactly as before (fork from master, merge to master) — the default is byte-for-byte the old behavior.
Where to find it
Where it is
- The Start session dialog (the one you get from the inbox "Start session" action). A Base branch dropdown appears below the Repo picker, but only once you've picked a project and that project has branches. It's a searchable dropdown that defaults to the project's current branch; a tooltip reads "The session forks from — and merges back into — this branch."
- The branch list is fetched live from the chosen project's git repo (its local branches, current branch first). If the folder isn't a git repo, the dropdown simply doesn't appear — it never errors.
How it behaves
The one boundary to know
This is delivered entirely through Omniscio's in-app merge — the path interactive
sessions already use when they end. The background auto-lander (which merges
ready-to-merge-tagged branches) is deliberately left untouched.
So do NOT tag a base-branch session ready-to-merge. If you do, the auto-lander lands
the branch to master (its normal, unchanged behavior) — and, critically, your chosen
target then never receives the work at all. This is stronger than "the lander used the
wrong target": once the lander has landed the branch, the later in-app merge is SKIPPED.
executeMerge (worktree-merge-queue.ts)
computes alreadyLanded = await isBranchDeliveredToBase(projectPath, branchName) — true when the
branch has reached the base by ANY route — and short-circuits to a
synthesized { success: true } without ever calling mergeWorktree, so the
landTarget it just read is never used. The worktree is retired down the shared success
path and the session is pushed as status: 'merged'.
Note which function that is, if you are debugging a skipped merge:
isBranchDeliveredToBase is the cold-path delivery check. It trusts the branch's
status: landed tombstone first and only then falls through to the auto-lander event ledger —
deliberately, so a land by land:catch-up, merge-all-ready or safe-merge, none of which
write an auto_lander_events row, still counts as delivered. The inner, ledger-only
isBranchAlreadyLanded also still exists, so an empty auto_lander_events does not prove the
skip could not have fired — read the tombstone too.
Net effect for exactly that combination: the UI reports success, and the branch you picked
is never touched. The work is not lost — it is on master — but it is not where you asked
for it. (The skip itself is correct in isolation: the lander replays commits with FRESH
SHAs, so a plain git merge would FALSE-conflict on already-landed changes. What is missing
is any handling of a landTarget that the land bypassed.)
An earlier version of this page — and invariant 5 of the contract — described this as "safe, just not the chosen target", which understated it. Landing into your branch happens via the normal session-end / Merge / CLI merge only when the auto-lander has not already landed the branch. Closing the gap properly is a lander/merge-queue behaviour change and is deliberately not taken here (contract invariant 5: "Do not re-solve this in the lander without an explicit decision").
Status & limits
- Shipped for the Start session dialog. The Quick Launch composer picker and the
CLI/API (
baseBranchonPOST /project/:name/new) are planned follow-ups. - Desktop + mobile (the picker reuses the standard searchable
<Select>; the branch-list read is mobile-reachable).
For agents
How it works (for agents)
- Fork point.
createWorktree(projectPath, sessionId, sessionName, options)(src/main/services/worktree/worktree-service.ts), where the fork point isoptions.baseBranch— a field ofCreateWorktreeOptions, alongsideexplicitTargetandprovenance. It is NOT a 4th positional string. Pass a bare branch name in that slot andoptions.baseBranchisundefined, so the worktree silently forks from the repo's resolved head and the user's chosen base is ignored. (The options object replaced a positional list that had reached three, four and six arguments across three subsystems, precisely because a future insertion would mis-bind silently.) It validates the chosen ref (git rev-parse --verify) and passes it as the start point ofgit worktree add -b <branch> --no-checkout <path> <baseRef>. A bad/typo'd branch fails with a clear "Base branch not found" error, never a raw git dump. With no pick and no default branch resolving, it forks fromHEAD, always passed as an explicit start point — with none, git guesses--orphanin an empty repository and dies against--no-checkout. A repository whoseHEADnames no commit (brand new, or its.gitdamaged) is refused before any worktree exists with a clear "Nothing to branch from" error —base-branch-invalid(400) onPOST /worktrees/create, never git's raw message — while any other failure of that check surfaces unchanged. And a create that succeeds is not a folder you can work in yet: on a busy box the route routinely answers202 status: 'creating', and even its200carries areadinessWarningwhilenode_moduleskeeps filling in behind the reply. Read it before you commit anything in the new folder. - Persistence. The chosen branch is saved on the session as
land_target_branch(a nullable column; NULL = master/today) so the merge step later knows where to land. - Merge back. When the session's worktree merges (on session-end, the Merge action,
or the CLI), the in-app merge queue reads that land target and merges into it via a
ref-only advance — it updates
refs/heads/<target>in git's object store and never touches a live working tree. If the target branch happens to be checked out in another worktree, it refuses (rather than corrupt that checkout) and reports it; a target branch that's gone is a harmless no-op. - Branch list IPC.
PROJECT_BRANCHES_GET(project:branches-get) returns{ branches, currentBranch }for a project; it's a read, safe to call from mobile.
The full invariants live in the repo's own base-branch-picker-contract.md (contributors only). The behavior that matters to a reader here: a base-branch session must not be tagged ready-to-merge, because the auto-lander would land it to base and the in-app merge into your chosen branch is then skipped.
Related
Base branches are one part of a session's git story: start-a-new-session.md covers the Start session dialog this picker lives in, session-land-status-strip.md shows where a finished session's branch sits in the auto-lander's queue, and auto-lander-dashboard.md is where the lander itself is watched, paused and resumed.
Last verified 2026-09-28