git.worktree_create¶
git.worktree_create makes a linked git worktree for a session. It creates a new branch for the worktree, or it attaches the worktree to a branch that exists. Then it checks out the tracked files and copies some ignored files into the new worktree.
A failed request can leave files on disk: see After a failure.
Request¶
| Parameter | Required | Meaning |
|---|---|---|
baseRepo |
yes | The repository. |
branchName |
yes | The branch to create. It is required with existingBranch too. An attach that succeeds does not use it. |
worktreePath |
yes | The directory of the new worktree. It must not exist. |
sourceBranch |
no | The name that the new branch starts from. The daemon reads it as a local branch and as origin/<name>. The default is the current branch. An attach to existingBranch does not use it. A name that matches no branch is not an error: see Response. |
existingBranch |
no | A local branch to attach to. If it names no local branch, the daemon creates branchName instead. If the attach fails, the daemon does the same. For example, git refuses an attach to a branch that is checked out in baseRepo. The request can then still succeed. branch in the response says which branch the worktree is on: see Response. |
worktreeRoot |
no | A location outside the repository. Windows refuses it with unsafe_path. |
timeoutMs |
no | A deadline that starts at the add. It stops only the checkout. The daemon tests it again after the add and after the copy step. With no value or 0 there is no deadline. |
shadowDirOff |
no | true turns off three things for this request: the filter rule of the checkout, the daemon's own read of the git config of the repository, and the settings copy. Any other value does nothing and is no error. See Git filters, Repository config refusals and The settings copy. |
Where the worktree can go:
- Without
worktreeRoot,worktreePathmust be an absolute path insidebaseRepo. The usual place is<baseRepo>/.claude/worktrees/<name>. - With
worktreeRoot,worktreePathmust be<worktreeRoot>/<directory>/<name>. A root that exists must belong to the user of the daemon. Its mode must have no write bit for others, or for a group other than the private group of that user. - Neither a component of the location nor the
<directory>can be.gitor.claude-managed-worktrees, in any letter case. The location must not resolve to/. It must not lie in a folder with a.gitentry, the git directory of the repository, or a session folder. The<directory>level must not hold the repository or be writable by every user.5fd08069refuses so in cells C-W3 to C-W20 of roundtier0-20and X1 and X2 of roundtier0-22(Linux VM). Not measured:.claude-managed-worktreesin the location, a reserved<name>, and the owner and group tests of the<directory>level. claustrum refuses all of them.
Response¶
Success:
{"success": true, "path": "/repo/.claude/worktrees/w1", "sourceBranch": "main", "branch": "feature"}
path is worktreePath as sent, after the ~ expansion. branch is the branch of the worktree. If the daemon attached to existingBranch, it is that branch. If not, it is branchName.
If the daemon created a branch, sourceBranch is the name that the start commit came from. The daemon reads that name as two refs: the local branch <name> and the remote-tracking ref origin/<name>. It fetches nothing. If both exist, the daemon selects one of them. It selects origin/<name> in most states. For example, a local branch that is behind origin/<name> or equal to it gives origin/<name>. The response holds the name as sent in each case.
If the value as sent named neither a local branch nor a branch of origin, that is not an error. The new branch then starts at HEAD, and the response holds the current branch, not the value as sent. On a detached HEAD, the response then has no sourceBranch. That applies to a request with a sourceBranch and to a request without one.
After an attach the response can have a sourceBranch, and it then says nothing about the worktree.
Failure:
Decide by errorCode first.
errorCode |
Meaning |
|---|---|
unsafe_path |
The daemon refuses a path. For example, worktreePath is relative, has a .. component, is outside baseRepo, or exists. A worktreeRoot that is not safe gives this code too. So does a worktree that git registered in an unexpected place. |
symlinked_component |
A directory between baseRepo and the worktree is a symbolic link, for example a symlinked .claude or .claude/worktrees. |
not_a_repo |
baseRepo is not a git repository, or git itself does not accept it. A bare baseRepo gets this code too while the daemon works on its settings copy. |
nested_base_repo |
baseRepo is inside a directory of managed worktrees, or the daemon does not accept it as a trust root. |
mkdir_failed |
The daemon cannot create a directory for the worktree. Or, with worktreeRoot, a git call or a .git test failed first. |
worktree_add_failed |
git worktree add or the checkout failed. The text holds the message of git, or a refusal of the daemon that came before the add. |
repository_only_filter |
The checkout needs a git filter that only the git config of the repository defines. See Git filters. |
timeout |
The deadline of timeoutMs expired. |
If the daemon does not accept the git config of the repository, the code is worktree_add_failed and nothing is made. For example, the format version of the repository is not 0 or 1. The texts are in Repository config refusals.
Three failures of the settings copy give worktree_add_failed too, and nothing is made. The test of the git of the host failed. The call that asks git about the repository failed. Or the daemon cannot make the folder of the copy. See The settings copy.
A request with no branchName gets the JSON-RPC error -32602.
The exact texts are in the error-string catalog.
After a failure¶
What stays on disk depends on where the request failed.
A request can create four things outside the worktree, and no failure removes them:
- directories above the worktree
- the registrations directory
<git dir>/worktrees - with
worktreeRoot, the marker file.claude-managed-worktreesat the<directory>level - with
worktreeRoot, the folder of a gone worktree that holds the root (5fd08069, cell C-W8 of roundtier0-20, Linux VM)
On Windows a folder .claude-new-worktree-<16 hex> can stay in the git directory when a step after the add fails.
A request can also remove a stale registration of this worktree path before the add. On Linux and macOS that covers the main repository of a linked baseRepo. Two creates through a repository and its linked worktree add in turn (5fd08069, cell T1 of round tier0-11, Linux VM).
| Failure | What stays of the worktree |
|---|---|
A refusal of the request or of a path (not_a_repo, nested_base_repo, symlinked_component, and unsafe_path for a path) |
Nothing, except the folder of a gone worktree above. |
The daemon refuses before it runs git worktree add (worktree_add_failed) |
Nothing. |
The daemon cannot create a directory (mkdir_failed) |
Nothing. |
git worktree add runs and fails (worktree_add_failed) |
If the worktree directory is empty, the daemon removes it. Files that the failed add wrote stay. A registration and a branch that the add made stay too. |
A test of the new worktree fails after the add. On Linux and macOS the tests come before the checkout and give unsafe_path. A worktree directory replaced during the checkout, or on Windows, gives worktree_add_failed. |
Everything, except an empty worktree directory. |
The checkout fails, the filter rule refuses, or the deadline expires (worktree_add_failed, repository_only_filter, timeout). Or the text ends with ; retry. |
The daemon removes the worktree directory and its registration. If this request created the branch, the daemon removes the branch too. The cases below the table are exceptions. |
A rollback leaves something in these cases:
- If the branch holds commits that no other ref reaches, the daemon keeps the branch. The response then has
"branchKept": true. - If a delete fails, the error text ends with a clause that says what remains. On Windows a registration that stays adds no clause.
- If something replaced the worktree directory, the rollback keeps the replacement and adds no clause. On Linux and macOS it also keeps a replaced registration directory (D24).
Git filters¶
A git filter is a program that git runs for a file of the checkout. If a file of the checkout uses a filter that only the git config of the repository defines, the request fails and the daemon rolls back:
{"success": false, "error": "Claude doesn't run the \"<name>\" git filter, because only this repository's own git config defines it. If you trust that filter, copy the [filter \"<name>\"] section from that config (usually .git/config) into ~/.gitconfig on the remote machine, then try again.", "errorCode": "repository_only_filter"}
A filter counts as defined where a smudge or a process key has a value. A filter that the user config, the system config or the environment of the daemon defines runs. If the repository defines that filter too, the values from outside the repository apply. If the repository adds a process key to a filter that has none outside it, the request fails with worktree_add_failed. With a git before 2.26, the daemon refuses a filter that a conditional include (includeIf) of the user config defines.
Three values of the repository pass: smudge = cat under each name, and git-lfs smudge -- %f and git-lfs filter-process under the name lfs. With smudge = cat the daemon starts no program. If the config of the repository itself names a program for git-lfs, the filter lfs does not run, wherever it is defined. A file under that filter then fails the request. A filter name with = in it, or one that is not valid UTF-8, fails the request with worktree_add_failed. So does a refused name of more than 200 bytes.
"shadowDirOff": true turns the rule off for one request. The daemon then runs the filter of the repository.
The settings copy¶
The daemon keeps its own copy of the git settings of the repository, and its git calls read that copy. The settings copy in the protocol reference has the states, the texts and the folder. These rules are for this method:
- If the directory
<git dir>/worktreesis missing, the daemon makes it before the add, with mode 0777 less its umask. It stays after a failed add. On Linux and macOS, the lastcore.sharedrepositoryline of the copy then sets its mode, unless that line isumask. Cells H1, S1, S2 and C2 of roundstier0-14totier0-16show this (Linux VM). macOS is not measured. - If the repository has
extensions.worktreeConfigand a file<git dir>/config.worktree, the new worktree gets its ownconfig.worktreefile. It holds the text of that file withoutcore.bareandcore.worktree. - A bare
baseRepogetsnot_a_repo. - On Windows the add writes the registration of the new worktree into a short-lived folder of the daemon. The daemon then copies it into
<git dir>\.claude-new-worktree-<16 hex>and renames that folder to<git dir>\worktrees\<name>. - On Windows, before that step, a create removes old entries of the git directory. Such an entry has a name that starts with
.claude-new-worktree-and a write time of 1 hour or more. The request did not make those entries. A junction goes as a junction, and its target stays. - A create holds the git directory open from its gate to its reply. The held git directory has the rules.
- In the state
shadow, a failedrev-parse --absolute-git-dirstops the create withgit worktree add failed: cannot locate the repository's git directory: exit status 128(5fd08069, cell V12 of roundtier0-10, Linux VM). The text after the last colon is the error of the call. macOS is not measured. - On Windows, a step before the add that fails gives
git worktree add failed: cannot prepare the repository's worktrees directory: <cause>, and theworktree addcall does not run. On Linux and macOS a symbolic link, a file or a folder that cannot be opened atworktreesdoes the same.5fd08069answers so in cell V1 of roundtier0-10, cells C1, C4 and C5 of roundtier0-24(Linux VM) and C5 of roundtier0-25m(macOS VM). - On Linux and macOS a linked
baseRepowhose entry in<git dir>/worktreesis a symbolic link gets the trust refusal as a failed add. WithoutworktreeRootits.claude/worktreesis made (5fd08069, cell C7 of roundtier0-24, Linux VM, withextensions.worktreeConfigand aconfig.worktreefile). Other forms are not measured.
"shadowDirOff": true turns the copy off for one request, and none of these steps runs. The same holds on a host whose git does not work on such a copy. With that member a link at worktrees refuses the create before the add (5fd08069, cell C2 of rounds tier0-24 and tier0-25m, Linux and macOS VMs).
The held git directory¶
In the state shadow a create holds the git directory of baseRepo open from its gate to its reply.
- The git directory must be a real directory at the gate. On Windows a junction or a directory symbolic link there is refused, on every git method without
"shadowDirOff": true. With that member both link kinds pass, and git works in the folder behind the link, as on5fd08069(Windows VM). On Linux and macOS a.gitthat is a symbolic link before the request is resolved first, and the daemon holds its target. A change of that link is a change of the path too. - The daemon's own deletes, renames, new folders and file writes inside the git directory go through the held folder. A folder that is put at the path while the create runs gets none of them, except the directory that the hold moves to (the rule for Linux and macOS below).
- The hold stops no rename of the git directory by another process. On Windows a rename of the repository folder fails from an earlier call of a create than on
5fd08069. - The steps of the create test that the path still names the held folder. A swap after
rev-parse --absolute-git-dirand before the add is refused before the add. Git itself goes by the path. - A swap after the read-tree call fails the create, and the registration gets no index. On Windows a swap after the first listing of the checkout fails it too. The text starts with
git worktree add failed (checkout):. The registration and the branch then stay, and the text says so. - On Linux and macOS two steps move the hold to a copy of the git directory, and
5fd08069goes on in it. A link to the copy is put there after the first call or afterrev-parse --abbrev-ref HEAD(Linux and macOS VMs). Or a real copy is put there whilegit worktree addruns (cell C6 of roundstier0-24andtier0-25m, Linux and macOS VMs). Another git directory gets the same rule (not measured). - On Linux and macOS, a
.gitthat became a file withgitdir: <another git directory>afterrev-parse --absolute-git-dirgivesgit worktree add failed: cannot locate the repository's git directory: lstat <baseRepo>/.git/commondir: not a directory. - On Linux and macOS, a
.gitthat is a symbolic link to a copy of the git directory right after a goodworktree addgivesrefusing to create worktree: <leaf> was not populated by git worktree addwithunsafe_path. Nothing is rolled back, and the.gitfile of the new worktree keeps the text that git wrote. - By the path stay: git itself, a request with
"shadowDirOff": true, and a host in the statefallback-gitor with no run folder. So does the step for an old registration when itsworktreesfolder is not in the held folder. On Linux and macOS no delete goes behind aworktreesfolder that is a link (from the code).
The copy of ignored files¶
After the checkout, the daemon copies two sets of files from baseRepo into the new worktree:
- The ignored files that the root file
.worktreeincludenames. That file uses the syntax of.gitignore. - The ignored files under
.claude/.
Neither set includes .claude/worktrees or the session state of Claude under .claude/, for example checkpoints, mailbox and scheduled_tasks.json. A new worktree does not inherit the session state of the repository.
A copy does not keep the mode of its source. The daemon creates each file with mode 0666 less its umask. So an executable file loses its execute bit, and a 0400 file gets a wider mode. The daemon also skips symbolic links. A copy does not replace a file or a link that the checkout made. Name configuration files in .worktreeinclude, not secrets or scripts.
A copy that fails does not fail the request. If the deadline of timeoutMs expires during the copy, the request fails with timeout.
Differences by system¶
| Linux and macOS | Windows | |
|---|---|---|
A junction between baseRepo and the worktree |
Does not apply. | Refused with mkdir_failed. |
| A stale registration of the worktree path before the add | If it is the one stale entry and is not locked, the daemon removes it. | The daemon removes it, with no such test. |
Differences from the reference¶
The pinned build 89cb6289 runs a git filter that only the repository defines. claustrum follows 5fd08069 and refuses it, with some differences: see Git filters. claustrum also follows 5fd08069 in the refusal of a repository config: see Repository config refusals. It follows 5fd08069 in the settings copy too, with some differences. These entries of the divergence catalog apply to this method:
- D2: a delete refuses a path that is the home directory or holds it. That covers the Windows removal of old
.claude-new-worktree-*entries. - D5: an optional deadline on the git calls. It is off by default.
- D24: on Linux and macOS, a rollback keeps a registration directory that something replaced after the add.