Appearance
daft start
Create a new branch and worktree
Usage
daft start [OPTIONS] <BRANCH_NAME> [BASE_OR_BRANCH] [BASE]
daft start <branch> [<base>] # local
daft start <repo> <branch> [<base>] # in another cataloged repo
daft start --repo <repo> <branch> [<base>] # explicit / script-safe
daft start --fork [<base>] [-n N] # anonymous throwaway worktree(s)The local form is equivalent to git worktree-checkout -b. All flags are the same as git worktree-checkout with -b implied.
Description
Creates a new branch and a corresponding worktree in a single operation. The new branch is based on the current branch, or on the base branch if specified.
By default, daft does not push the new branch to the remote. To enable pushing and upstream tracking, set daft.checkout.push true or use daft config remote-sync --on. You can also pass --local to skip remote operations for a single invocation regardless of config.
When the push is enabled, the repo's pre-push hook runs only when the push introduces new commits; a ref-only push of already-pushed commits skips it (configurable via daft.checkout.pushVerify: auto, always, or never — see Git Hooks).
Creating in another repo
daft start takes a leading repo catalog target, mirroring daft go <repo> <branch>. How the names are read:
| Form | Meaning |
|---|---|
daft start A | Always local: new branch A (a lone repo name is never a target) |
daft start A B | Decided local-first, in order — see below |
daft start A B C | Always cross-repo: branch B in repo A, based on C (a catalog miss is a hard error) |
daft start --repo A B [C] | Explicit cross-repo — for repo names shadowed by local branches, and for scripts |
Two names is the only ambiguous arity. It resolves in this order, and the first rule that matches wins:
Ais an existing local branch — the local reading is kept and fails fast as "already exists" (with a--repohint whenAis also cataloged).Bresolves to a commit here (branch, remote-tracking ref, tag, SHA) — this is the ordinary<branch> <base>form. A repo-shapedAdoes not hijack it:daft start api release-2creates local branchapioffrelease-2.Anames the repo you are standing in — a redundant qualifier, not a hop: the branch is created here on the usual local base (the current branch), and-cis not refused.Ais a live cataloged repo — createBover there.- Otherwise local: branch
Abased onB.
Anything meaningful in the current repository wins over a catalog match, so a new branch name that happens to equal a repo name never silently retargets. The escape when you really do want a cross-repo branch whose name collides with a local ref is --repo. The guess matches names only — paths (and UUIDs) work in the explicit forms.
A repo that is cataloged but whose directory moved, or that was removed with daft repo remove, is reported the same way daft go reports it — it is never quietly reinterpreted as a local branch name.
Cross-repo semantics:
- The resolved destination is announced before any work:
Creating branch 'X' in 'repo' (path) — based on 'base'(--quietopts out). - Without a base, the branch is based on the target repo's default branch (never whatever happens to be checked out there). An explicit base must exist in the target repo.
- The shell lands in the new worktree over there, and
daft go -hops back. - The target repo's hooks run under its trust; an untrusted target skips them with a notice.
-c/--carrycannot cross repositories (hard error);-xruns in the target repo's new worktree; a relative--atresolves inside the target repo.
Anonymous forks (--fork)
daft start --fork mints a fork: an anonymous throwaway worktree pinned at a position — detached HEAD, no branch, no tracking, no push, named by the system. With --fork the positionals reshape to just [<base>] (defaulting to the current position): the system owns the name, so there is no branch slot.
bash
daft start --fork # fork the current position
daft start --fork --carry # ...including uncommitted changes
daft start master --fork # fork master's position
daft start --fork -n 3 # three private forks, one command
daft start v1.18.0 --fork -x 'npm ci' # fork a tag and set it upBecause the name is the system's, the path is the result: each created worktree's path prints bare on stdout, one per line, while all narration goes to stderr. wt=$(daft start --fork) is the whole scripting integration. A single fork cd's the shell into it; with -n greater than one there is no single destination and the shell stays put.
Forks are private: unlike the canonical sandbox daft go <commit-ish> opens (shared, idempotent — revisits land in the same worktree), a fork is never matched by go's resolution and is reachable only by its printed name. That is what makes them safe for parallel agents: three concurrent daft start --fork invocations mint three distinct worktrees with zero coordination — names are claimed atomically by directory creation.
The full loop — fork a branch's position several times, commit in each, then merge the keepers back with daft merge worktrees/<dirname>/HEAD — is walked through in Anonymous worktrees.
Naming follows daft.start.forkNaming:
derived(default) —<source>-fork,<source>-fork-2, … Self-describing provenance indaft list.memorable— adjective-noun handles (brave-otter), collision-sparse and easier to say and retype.
Remove a fork with daft remove <name>. Commits made inside it are protected for as long as the worktree exists; removal refuses when HEAD moved off the pinned commit until you promote (daft start <new-branch> from inside the fork — the detached HEAD becomes the base) or force.
Coordinated fan-out (--with-related)
daft start <branch> --with-related also creates the branch in every repo the current repo's daft.yml relations: manifest points at — see Coordinated changes. Combined with a repo target, the fan-out is rooted at the target: daft start api feat/x --with-related creates feat/x in api and in the repos api's manifest declares, and the shell lands in api's new worktree.
Arguments
| Argument | Description | Required |
|---|---|---|
<BRANCH_NAME> | Name of the new branch — or a cataloged repo to create it in, when two or more names are given | Yes |
[BASE_OR_BRANCH] | Base branch (defaults to the current branch) — or, when it names no ref here, the new branch inside the repo | No |
[BASE] | Base branch inside the repo (three-name form); must exist there | No |
Options
| Option | Description | Default |
|---|---|---|
--repo <REPO> | Create the branch in a repository from the catalog (for repo names shadowed by local branches) | |
--with-related | Also create the branch in every related repo (relations manifest), each based on its own default branch | |
--fork | Mint an anonymous throwaway worktree pinned at [<base>] (defaults to the current position): detached, system-named, no branch, no push; the created path prints on stdout | |
-n, --count <N> | Create N fork worktrees (requires --fork; one path per line on stdout; the shell stays where it is) | 1 |
--local | Skip all remote operations (no fetch, no push) for this invocation | |
--skip-hooks <SELECTOR> | Skip hooks this run (all | a hook name like worktree-post-create | tag:<tag> | <job>); repeatable/comma-separated | |
--hooks <MODE> | How this run's hook phase executes: auto (honor each job's background:), foreground (run every job inline and wait; a promoted job's failure fails the hook), background (dispatch every job and return, where daft is done with the phase and it declares no ordering), off (same as --skip-hooks all) | auto |
-c, --carry | Apply uncommitted changes from the current worktree to the new one | |
--no-carry | Do not carry uncommitted changes | |
-x, --exec <EXEC> | Run a command in the worktree after setup completes (repeatable) | |
--no-cd | Do not change directory to the new worktree | |
-v, --verbose | Show detailed progress | |
-q, --quiet | Suppress non-error output |
Global Options
| Option | Description |
|---|---|
-h, --help | Print help information |
-V, --version | Print version information |
See Also
- daft go to open an existing branch
- daft config to configure remote sync behavior
- git worktree-checkout for full options reference