Skip to content

[Bug]: Worktree creation fails at configureBaseRef when .git/config is locked and leaves an orphan worktree + branch #11735

Description

@erannave

Steps to reproduce

Deterministic, without any AI provider involved:

  1. Open any git repository as a T3 Code project (WSL runtime).
  2. On the host, simulate a held config lock: touch <repo>/.git/config.lock (any pre-existing file works; git's config writer uses O_CREAT|O_EXCL on that path).
  3. Create a new thread with a worktree.

How it actually happens in practice (no manual touch): Claude Code's Linux/WSL sandbox deny-writes <main-repo>/.git/config.lock with a bubblewrap bind mount and materializes a 0-byte, mode 0444 placeholder on the host for the duration of every sandboxed Bash command, in the main repo's .git even when the thread runs in a linked worktree (anthropics/claude-code#78818, confirmed by a maintainer). Any T3 worktree creation that overlaps a Bash command in another thread hits step 2 for free, and the provider chosen for the new thread is irrelevant: my last failure was a Codex thread, created while a Claude thread in another worktree was 5 minutes into a 7-minute test run. With three busy threads I got 7 failures in one afternoon.

Expected behavior

Either of:

  • createWorktree treats git config branch.<new>.gh-merge-base <base> as best-effort: log a warning, keep the worktree, let the thread start (the merge base can be re-derived later, resolveBaseBranchForNoUpstream already falls back to the default branch).
  • Or, if the config write is required, roll back the git worktree add and the branch so no orphan is left behind, and retry the config write a few times (the lock is transient by nature).

Also: surface git's stderr in the error (#4380). "Git command exited with a non-zero status." hid a one-line could not lock config file .git/config: File exists that would have made the diagnosis trivial.

Actual behavior

GitVcsDriver.createWorktree runs git worktree add -b <new> <path> <ref> (succeeds), then git config branch.<new>.gh-merge-base <base> in the main repo, which exits 255. The whole command fails with:

Git command failed in GitVcsDriver.createWorktree.configureBaseRef (/home/eran/batalyse/batalyse): Git command exited with a non-zero status.

The thread is never created, but the worktree directory under ~/.t3/worktrees/<repo>/t3code-<id> and the branch t3code/<id> remain. I had to clean up 4 orphans by hand (git worktree remove + git branch -D); nothing in the UI shows them.

How bad is this in practice?

Major degradation or frequent failure

Version or commit

T3 Code (Alpha) 0.0.40, WSL runtime sha256-02c38816d0b6722af4ec74c48982ab009586fe7f84274cc757fe8f039c4784db

Environment

Windows 11 desktop app, WSL2 Ubuntu (kernel 6.18.33.2-microsoft-standard-WSL2), git 2.53.0, Claude Code 2.1.270 as provider with the sandbox enabled

Logs or stack traces

{"type":"effect-span","name":"createWorktree","durationMs":1241.5,"exit":{"_tag":"Failure","cause":"GitCommandError: Git command failed in GitVcsDriver.createWorktree.configureBaseRef (/home/eran/batalyse/batalyse): Git command exited with a non-zero status.\n    at createWorktree (file:///home/eran/.t3/wsl-runtime/sha256-02c38816.../apps/server/dist/bin.mjs:83455:66)\n    at createWorktree (definition) (file:///home/eran/.t3/wsl-runtime/sha256-02c38816.../apps/server/dist/bin.mjs:83128:25)"}}
{"type":"effect-span","name":"GitVcsDriver.createWorktree","durationMs":1216.7,"attributes":{"git.operation":"GitVcsDriver.createWorktree","git.cwd":"/home/eran/batalyse/batalyse","git.args_count":6},"exit":{"_tag":"Success"}}
{"type":"effect-span","name":"GitVcsDriver.createWorktree.configureBaseRef","durationMs":10.8,"attributes":{"git.operation":"GitVcsDriver.createWorktree.configureBaseRef","git.cwd":"/home/eran/batalyse/batalyse","git.args_count":3},"exit":{"_tag":"Success"}}

(The last span reads Success because executeGit runs with allowNonZeroExit: true and checks the exit code outside the span, so the trace never records the failing exit code or stderr.)

Reproduced by hand on the host while the placeholder existed:

$ git config branch.zz-probe.gh-merge-base main; echo exit=$?
error: could not lock config file .git/config: File exists
exit=255
$ ls -la .git/config.lock
-r--r--r-- 1 eran eran 0 Sep 14 13:21 .git/config.lock

Workaround

Delete the stale <repo>/.git/config.lock when it is a 0-byte 0444 file (a bubblewrap placeholder, not a git lock), create the worktree while no other thread is running a Bash command, and remove orphans with git worktree remove <path> && git branch -D t3code/<id>.

Activity

  1. juliusmarminge commented on Sep 14, 2026

    @juliusmarminge
    Member

    Thanks for the deterministic repro and the lock vs. orphan split. This checks out on current main (6f00d3881) and is a real server bug, not a wash of #4380 or a duplicate of the other worktree-create failures.

    What’s going wrong

    GitVcsDriver.createWorktree in apps/server/src/vcs/GitVcsDriverCore.ts does three things after a successful add:

    1. git worktree add -b <new> <path> <ref> — this is what creates the directory and t3code/<id>.
    2. Submodule update --init --recursive — already best-effort; failure does not fail the call.
    3. git config branch.<new>.gh-merge-base <base> in the main repo (GitVcsDriver.createWorktree.configureBaseRef) — hard-required. No retry, no rollback.

    That last write needs .git/config.lock. Git uses O_CREAT|O_EXCL, so any file at that path exits 255 with could not lock config file .git/config: File exists. Your hand repro (touch .git/config.lock or the 0-byte mode 0444 bubblewrap placeholder) matches that.

    git worktree add -b without --track can succeed without taking that lock; the explicit git config is the first write that needs it. So the add commits, then the metadata write fails, and the caller sees:

    Git command failed in GitVcsDriver.createWorktree.configureBaseRef (...): Git command exited with a non-zero status.
    

    The span for configureBaseRef still says Success because executeGit runs the child with allowNonZeroExit: true and only fails after the span. GitCommandError stores stderrLength and a static detail, not git’s stderr — same funnel as #4380.

    The write is not required for a usable worktree. resolveBaseBranchForNoUpstream already tries gh-merge-base, then the default branch, then main/master.

    Why the orphans stay

    New-thread bootstrap in apps/server/src/ws.ts creates the thread, then calls createWorktree. onWorktreeClaimed runs after the add and before configureBaseRef. On failure, cleanupCreatedThread() deletes the thread. Worktree removal runs only on user cancel (Cause.hasInterruptsOnly). A GitCommandError after the claim leaves ~/.t3/worktrees/<repo>/t3code-<id> and branch t3code/<id>. git worktree remove also does not delete the branch, so even cancel can leave the ref.

    The RPC vcs.createWorktree path has no bootstrap cleanup at all, so a driver-level fix is the right place.

    Trigger vs. bug

    The common real-world lock is Claude Code’s Linux/WSL sandbox deny-writing <main-repo>/.git/config.lock for every sandboxed Bash command, including from another thread’s worktree (anthropics/claude-code#78818). That is an upstream aggravator. T3 still should not fail thread create or leave orphans when a best-effort config write cannot take the lock. The new thread’s provider is irrelevant.

    Why this isn’t a duplicate

    No open PR fixes #11735.

    Suggested fix

    1. Treat configureBaseRef as best-effort, same as submodule checkout: optional short retry for a transient lock, then log a warning and return the worktree.
    2. If any post-add step stays required, roll back with git worktree remove --force and delete the new branch so a failed call is a no-op.
    3. In ws.ts, remove a claimed worktree on any bootstrap failure, not only cancel.

    Workaround remains: delete a stale 0-byte 0444 .git/config.lock, create the worktree while no other thread is in Bash, then git worktree remove <path> && git branch -D t3code/<id>.

    Triage: confirmed bug on apps/server. Not a duplicate. Accepting for a server fix.

  2. added
    bugSomething is broken or behaving incorrectly.
    acceptedfeature request accepted
    via-triageFiled through npx t3 triage
    on Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions