worktree branch
Create a new branch in a dedicated worktree.
Usage
worktree branch <name>
worktree branch <name> --source <remote-branch>
worktree branch --github <issue-number>
worktree branch --github <issue-number> --assign
worktree branch --github <issue-number> --no-assign
worktree branch --jira <issue-id>
worktree branch <name> --agent "<prompt>"
worktree branch <name> --agent-file <path>
worktree branch <name> --agent-stdin
worktree branch <name> --no-agent
worktree branch <name> --no-open
worktree branch <name> --install
worktree branch <name> --no-install
worktree branch <name> --jsonExamples
worktree branch feature/add-bulk-actions
worktree branch feature/add-bulk-actions --source origin/release/1.4
worktree branch --github 42
worktree branch --github "#42"
worktree branch --github 42 --assign
worktree branch --github 42 --no-assign
worktree branch --jira DEV-123
worktree branch --jira dev-123
worktree branch feature/add-bulk-actions --agent "add bulk actions to the table"
worktree branch --github 42 --agent "implement the issue"
worktree branch --github 42 --agent-file brief.md
cat brief.md | worktree branch --github 42 --agent-stdin
worktree branch --github 42 --no-agent
worktree branch feature/add-bulk-actions --no-open
worktree branch feature/add-bulk-actions --install
worktree branch feature/add-bulk-actions --no-install
worktree branch --github 42 --json --agent-file brief.mdGitHub Issue Branching
Use the --github flag to create a suggested branch name from an issue in the current repository.
- The issue number can be passed as
42or"#42". - The generated name is pre-filled in the prompt and still editable. A non-interactive run takes it as it is.
- If issue types are configured in the repository, these optional prefixes are applied:
branchPrefix.featurefor issue typeFeaturebranchPrefix.bugfixfor issue typeBugbranchPrefix.chorefor issue typeTask
If no prefix is configured, worktree falls back to an empty prefix and still generates a valid branch name.
The name is <prefix><number>-<slug>. The title becomes a lowercase slug of letters, digits and dashes, cut to at most 48 characters at the last
dash, so the name ends on a whole word. A single word longer than 48 characters is cut at 48. The name is the
same whether or not you are asked, so one issue never gives two different branches.
GitHub authentication is resolved in this order:
northguild.worktree.github.tokenfrom local git config.gh auth tokenif GitHub CLI is installed and authenticated.- Interactive token prompt, which is then saved in local git config.
A non-interactive run has no token prompt. It exits 2 with one line on stderr, which never includes a token:
worktree: no default for a GitHub token; pass `worktree config github.token <token>` or run `gh auth login`Assigning the issue to yourself
--assign assigns the issue to the authenticated user as part of creating the branch, and --no-assign
skips it for that one run. Without either flag the decision is made in this order:
--assignor--no-assign.github.autoAssign:trueassigns,falsenever does.- A non-interactive run assigns. The default is not saved, so one unattended run does not settle the key.
- Otherwise you are asked once, and your answer is saved to
github.autoAssignso the question is not repeated.
A configured false is respected in every mode; pass --assign to override it for one run.
worktree branch --github 42 --assign
worktree branch --github 42 --no-assign
worktree config github.autoAssign true # always assign, stop asking- Assigning requires a token with push access to the repository. Reading an issue does not, so a public
repository that
--githubhas always worked against without a token may ask for one the first time you assign — the resolution order above still applies, so an authenticatedghsupplies it silently. - Success is confirmed against the API response rather than assumed. If the issue comes back without you on
it,
worktreesays so instead of reporting success — GitHub can accept an assignee change and ignore it when the token lacks push access. - A failed assignment never stops the branch. The worktree is created either way; you get a warning, not an error.
- The assignment happens before the worktree is created, so the question is never buried behind creation output or a launched editor. Assigning is idempotent — GitHub does not replace existing assignees — so retrying after a failed creation is harmless.
--assignand--no-assignboth require--githubor--jira; on their own there is no issue to act on.--assign --jirawarns that Jira assignment is not supported yet and carries on.
Installing dependencies
After the worktree exists and its env files are copied, branch can install its dependencies, before
anything is opened or any agent is started, so neither lands in a tree without them.
-
The command is
postCreatewhen it is set. Otherwise it is inferred from the first lockfile found in the new worktree:Lockfile Command pnpm-lock.yamlpnpm install --frozen-lockfilepackage-lock.jsonnpm ciyarn.lockyarn installbun.lockbun install --frozen-lockfilebun.lockbis not recognised.yarn installhas no lock flag because classic and berry disagree on it. -
When it runs: a non-interactive run installs by default. A terminal run installs only when
postCreateis set or--installis given.--no-installswitches it off for the run, whatever else is set. -
How it runs: by argv in the new worktree, never through a shell, with its output on stderr and a 10 minute limit.
-
Nothing to run: with no
postCreateand no lockfile, or when the program is not onPATH,branchsays why on stderr and carries on. -
A failed install (non-zero exit, or the limit reached) keeps the worktree, opens nothing, starts no agent, and exits
1. Nothing is rolled back. Fix the install in the worktree and runworktree open <branch>, or runworktree remove <branch> -fand re-runbranchwith--no-install. Re-runningbranchas it was fails, because the branch already exists.
checkout has no install step.
Jira Issue Branching
Use the --jira flag to create a suggested branch name from an issue in your Jira workspace.
- The issue ID can be passed in any case (
dev-123,DEV-123, etc.). - The generated name is pre-filled in the prompt and still editable.
- If issue types are configured in the repository, these optional prefixes are applied:
branchPrefix.featurefor issue types likeFeature,Story, and most non-bug/chore typesbranchPrefix.bugfixfor issue typeBugbranchPrefix.chorefor issue typeTask
If no prefix is configured, worktree falls back to an empty prefix and still generates a valid branch name.
Jira authentication requires three configuration keys:
northguild.worktree.jira.host- Your Jira workspace URL (e.g.,https://company.atlassian.net)northguild.worktree.jira.email- Your Jira email addressnorthguild.worktree.jira.apiToken- Your Jira API token
Agent Dispatch
Use the --agent flag (short: -a) to hand the new worktree to a coding agent with a prompt, called the
brief. The brief can come from one of three places, and they are mutually exclusive:
| Flag | The brief is |
|---|---|
--agent "<text>" | the flag’s value |
--agent-file <path> | the whole of a file, which has to exist and be a regular file |
--agent-stdin | the whole of piped stdin; a terminal on stdin is refused rather than waited on |
worktree branch feature/add-bulk-actions --agent "add bulk actions to the table"
worktree branch --github 42 --agent-file brief.mdAn empty brief, or one over 131,071 bytes (the most a single argument can carry on Linux), is rejected before anything is created. The brief is delivered verbatim, so quotes, apostrophes, semicolons and newlines in it reach the agent unchanged and no shell interprets it.
There is one agent per worktree, started one of two ways:
- When the worktree opens as a Herdr space (
openerherdr), the agent is started in the space’s pane withherdr agent start, and the brief is then submitted to it withherdr agent prompt, once, after the agent is ready. The brief is never part of the start command.- The agent kind is
herdr.agent, or else the programagent.commandnames. With a brief and neither, and Herdr as the opener and installed, the run exits2before the worktree is created, withworktree: no default for the agent kind; pass `worktree config herdr.agent <kind>`. - When
agent.commandnames the same program, its arguments are passed to the agent, minus--bgand--background(a note says so on stderr): Herdr runs the agent in the pane, so a background flag would take it out of sight. - For
claude,--name <session>is added. The session name is<repo>-<branch>, lowercased, with every run of characters outsidea-z,0-9and-turned into-, and never shortened. It is printed on stderr. Herdr’s own handle for the agent is separate and capped at 32 characters. - If starting the agent fails, or the brief cannot be delivered, a warning says so (a failed delivery names the brief by its size, never its text), the worktree and
space are kept, and the run exits
1.
- The agent kind is
- Otherwise (editor opener,
openernone, or Herdr failing to open)agent.commandis launched detached with the brief as its last argument and the new worktree as its working directory. It is started and left running;worktreedoes not wait for it and its output does not appear here. With a brief, noagent.commandand a run Herdr will not open (editor,none,--no-open, orherdrnot on yourPATH), the run exits2before the worktree is created, withworktree: no default for the agent command; pass `worktree config agent.command "<command>"`. If Herdr was expected to open and failed, and there is noagent.commandto fall back to, the worktree is kept and the run exits1.
--no-agent opens the worktree as usual but starts no agent. --no-open creates the worktree and prints
Worktree created at <path> without calling Herdr or the editor, for this run only, like opener none;
an agent is still started, detached, when a brief is given, unless --no-agent says otherwise.
checkout takes --agent and follows the same rules.
Non-interactive runs
A run is non-interactive when stdin is not a terminal, CI is set (and is not empty, 0 or false),
--non-interactive or --yes (-y) is given, or --json is given; every command accepts the two flags. It never prompts and never
animates; progress goes to stderr as plain lines.
- A prompt with a default takes it: the issue-derived branch name. An unset
github.autoAssignassigns the issue (without saving the key);falseor--no-assignstill skips it. - A prompt with none fails at once, exit
2, with one line on stderr:worktree: no default for <value>; pass <flag>. Here that is the branch name (<branchName>), and a--sourcewithout theorigin/prefix (--source origin/<branch>). - Dependencies are installed by default, from
postCreateor the lockfile;--no-installskips it. - Missing config is not offered to
config:defaultSourceBranchunset warns and falls back toorigin/main.
What It Does
- Assigns the issue to you, when creating from
--githuband assignment is enabled. - Creates a branch from your configured source branch.
- Adds a worktree under
<repo>.worktrees/<branch-name>. - Copies the gitignored env files into the new worktree — see Environment Files.
- Installs dependencies in it, when enabled. A failure stops here with exit
1. - Opens the worktree in your configured editor (if configured), or as a Herdr space when
openerisherdr; withopenernoneor--no-open, opens nothing and printsWorktree created at <path>. - Starts the coding agent and delivers the brief (with
--agent,--agent-fileor--agent-stdin), unless--no-agent— see Agent Dispatch.
JSON output
--json makes stdout carry exactly one JSON document and nothing else, on one line, for a script or
another agent to parse. It implies a non-interactive run. Everything meant for a
person goes to stderr instead: the spinner lines, the env-file list, notes and warnings.
{"path":"/abs/repo.worktrees/70-headless-agent-mode","branch":"70-headless-agent-mode","source":"origin/main",
"issue":{"provider":"github","number":70,"url":"https://github.com/acme/demo/issues/70"},"assigned":true,
"envFilesCopied":["docs/.env.local"],
"installed":{"ran":true,"command":"pnpm install --frozen-lockfile","inferred":true,"ok":true},
"herdr":{"space":"w5","pane":"w5:p1","agent":"wt-70-headless-agent-mode"},
"agent":{"name":"demo-70-headless-agent-mode","kind":"claude","command":["claude","--name","demo-70-headless-agent-mode"],"prompted":true},
"warnings":[]}| Key | Value |
|---|---|
path, branch, source | the worktree’s absolute path, its branch, and the ref it was created from |
issue | {provider:"github", number, url} or {provider:"jira", key, url}; null when no issue was used. url is null when it is not known |
assigned | true or false once an assignment was attempted; null when none was (no issue, --no-assign, or a configured false) |
envFilesCopied | the env files copied, relative to the repository root; [] when there were none |
installed | {ran:true, command, inferred, ok}, plus reason when ok is false; {ran:false, reason} when the step was skipped |
herdr | {space, pane, agent} for the Herdr space that was opened; null when Herdr was not opened. agent is Herdr’s handle, null when no agent was started in the pane |
agent | {name, kind, command, prompted} for the agent that was started; null when none was. command leaves out the brief. name is the session name, null where worktree named nothing (a detached start) |
warnings | what went wrong without stopping the run: a failed assignment, a Herdr or agent failure, missing config |
A failed install still prints the document, with installed.ok false and the message in warnings, and
exits 1: the worktree exists, so its path is reported rather than left out of an error. A brief that was
given and reached no agent is reported the same way, with agent null or agent.prompted false —
agent.prompted is the receipt that the agent has the brief.
A failure otherwise prints a different document, still one line on stdout, and exits non-zero. A line describing it goes to stderr as usual:
{"error":{"code":"missing_value","message":"no default for the branch name; pass <branchName>","details":{"value":"the branch name","flag":"<branchName>"}}}code | Means | Exit |
|---|---|---|
missing_value | a value with no default was not given; details names it and the flag | 2 |
invalid_value | a value or flag combination was refused: a bad branch name, an empty brief, a malformed --github or --jira issue id | 2 |
not_found | something named does not exist, such as the --source branch | 2 |
timeout | a call that is bounded did not answer in time | 1 |
failed | anything else | 1 |
No token or password is ever printed: URL credentials and token-shaped strings are masked in every message.