Skip to content

[Bug]: "Start from origin" aborts thread bootstrap when the base branch has no remote-tracking ref #8191

Description

@lumberman

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

prepareWorktree only checks that the origin remote exists before resolving the base ref against it. It never checks that the base branch has a remote-tracking counterpart. Any base branch that exists locally but was never pushed therefore fails the whole bootstrap.

This is the same failure class as #5408, but #5556 only fixed the "repository has no origin at all" case. A repo with a perfectly healthy origin still fails here.

  1. Open a project whose repository has an origin remote (so the fix(server): skip origin fetch when creating worktrees in repos without an origin remote #5556 guard passes and the fetch succeeds).
  2. Set the project's new-thread mode to New worktree and leave Start from origin enabled.
  3. Start a thread based on main. It works. T3 Code creates worktree branch t3code/<hash> locally; that branch is never pushed.
  4. Open a new-thread draft and, in the base-ref picker, select that local-only branch as the base (the toolbar renders it as From origin/t3code/… — see below).
  5. Enter a prompt and send.

Reproduced deterministically on the affected repo — every send with that base branch fails, and switching the base back to main succeeds immediately.

Minimal shell repro of the failing step:

git -C <repo> remote -v                 # origin exists
git -C <repo> branch feature/local-only # never pushed
git -C <repo> rev-parse --verify "refs/remotes/origin/feature/local-only^{commit}"
# fatal: Needed a single revision      (exit 128)

Expected behavior

When the base branch has no remote-tracking ref, prepareWorktree should fall back to the local ref instead of aborting — the same way #5556 falls back when there is no origin at all. Alternatively, "Start from origin" should be disabled (or the label should not read origin/<branch>) for a base branch with no remote counterpart.

Creating a worktree thread on top of an unpushed branch is a completely ordinary thing to do — it is exactly what happens when you start a follow-up thread on the branch of an earlier T3 Code thread, since T3-generated t3code/* branches are local until you push them.

Actual behavior

Bootstrap aborts at resolveRemoteTrackingCommit and the provisional thread is rolled back ~330 ms after it was created:

thread.created                          (server:bootstrap-thread-create:…)
GitCommandError: Git command failed in GitVcsDriver.resolveRemoteTrackingCommit
thread.deleted                          (server:bootstrap-thread-delete:…)

The failing command is built here — the base ref is passed straight through with an origin/ prefix and rev-parse --verify is expected to succeed:

apps/server/src/vcs/GitVcsDriverCore.ts:2958-2974 (main @ 5d76653)

const remoteRefName =
  parsedRemoteRef?.remoteRef ?? `${input.fallbackRemoteName}/${input.refName}`;
const commitSha = yield* runGitStdout("GitVcsDriver.resolveRemoteTrackingCommit", input.cwd, [
  "rev-parse",
  "--verify",
  `refs/remotes/${remoteRefName}^{commit}`,
]).pipe(Effect.map((stdout) => stdout.trim()));

and the caller, where the only guard is remoteExistsapps/server/src/ws.ts:1011-1028 (main @ 5d76653):

const startFromOrigin =
  bootstrap.prepareWorktree.startFromOrigin === true &&
  (yield* gitWorkflow.remoteExists({
    cwd: bootstrap.prepareWorktree.projectCwd,
    remoteName: "origin",
  }));
if (startFromOrigin) {
  yield* gitWorkflow.fetchRemote({ /* … */ });
  const resolvedRemoteBase = yield* gitWorkflow.resolveRemoteTrackingCommit({
    cwd: bootstrap.prepareWorktree.projectCwd,
    refName: bootstrap.prepareWorktree.baseBranch,
    fallbackRemoteName: "origin",
  });
  worktreeBaseRef = resolvedRemoteBase.commitSha;
}

The UI does not gate the option either — resolveBranchTriggerLabel renders From origin/<branch> based only on resolvedActiveBranchIsRemote === false, with no check that refs/remotes/origin/<branch> actually exists (apps/web/src/components/BranchToolbar.logic.ts:183-189). So the picker advertises a base ref that cannot be resolved.

Compounding symptom. Because the rollback soft-deletes the provisional thread while the client keeps the same pre-allocated threadId, the retry surfaces an unrelated-looking error instead of the actionable git one:

OrchestrationCommandInvariantError: Orchestration command invariant failed (thread.create):
Thread '20877f92-…' already exists and cannot be created twice.

That is the #4647 / #5721 stale-draft-ID behavior, still observed on 0.0.33 — noting it only because it is what the user actually sees. The git stderr is never surfaced in the UI, so the visible error points nowhere near the real cause. Four consecutive drafts were burned this way in my state.sqlite, each created and deleted within ~330 ms:

thread created_at deleted_at
20877f92-… 10:08:34.077 10:08:34.406
9cae6597-… 09:59:25.674 09:59:25.996
4fd16c01-… 09:56:57.886 09:56:58.271
c57fccdb-… 09:56:36.309 09:56:36.665

All four had "branch": "t3code/design-watch-evolution-page" in their thread.created payload — a local-only branch created by an earlier T3 Code thread. The last thread that succeeded had "branch": "main".

Impact

Blocks work completely

Version or commit

T3 Code 0.0.33 (AUR t3code-bin 0.0.33-2). The prepareWorktree code path above is unchanged on main @ 5d76653, so this should still reproduce on nightly.

Environment

Arch Linux (kernel 7.1.8), Wayland/Hyprland, provider claudeAgent (claude-opus-5). Settings: defaultThreadEnvMode: "worktree", newWorktreesStartFromOrigin: true.

Logs or stack traces

# ~/.t3/userdata/logs/server.trace.ndjson — root cause
GitCommandError: Git command failed in GitVcsDriver.resolveRemoteTrackingCommit (<repo>): Git command exited with a non-zero status.
    at resolveRemoteTrackingCommit (…/apps/server/dist/bin.mjs:44202:145)
    at resolveRemoteTrackingCommit (definition) (…/apps/server/dist/bin.mjs:34228:45)
    at ws.rpc.orchestration.dispatchCommand (…/apps/server/dist/bin.mjs:35718:111)

OrchestrationDispatchCommandError: Git command failed in GitVcsDriver.resolveRemoteTrackingCommit (<repo>): Git command exited with a non-zero status.
    at toBootstrapDispatchCommandCauseError (…/apps/server/dist/bin.mjs:48302:63)

# then, on retry of the same draft — what the user actually sees
OrchestrationCommandInvariantError: Orchestration command invariant failed (thread.create): Thread '20877f92-…' already exists and cannot be created twice.
    at invariantError (…/apps/server/dist/bin.mjs:22122:9)
    at requireThreadAbsent (…/apps/server/dist/bin.mjs:22164:21)
    at orchestration.command.thread.create (…/apps/server/dist/bin.mjs:23743:18)

Workaround

Any one of:

  • Turn off Start from origin for the project (newWorktreesStartFromOrigin: false) — the worktree then branches off the local ref and thread creation succeeds.
  • Push the base branch once (git push -u origin <branch>) so the remote-tracking ref exists.
  • Pick a base branch that does exist on origin (e.g. main).

An already-wedged draft still needs the localStorage cleanup from #4647.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions