# \[Bug\]: Scheduled tasks that launch a fresh thread always branch from \`main\` and fail every run in repos without it · Issue \#16183 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/issues/16183)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: backlog

Priority: 0

## Description

[Discussion and activity](https://git.cafe/pingdotgg/t3code/issues/16183)

---

## Summary

A scheduled task created through the Orchestrator MCP `schedule_task` tool that launches a fresh thread per run (`bindToCurrentThread: false`, or a `projectId` other than the caller's, which defaults to unbound) always provisions that thread's worktree from a hard-coded `baseRef: "main"` with `startFromOrigin: true`. In a Git project whose default branch is not `main` (for example `master` or `trunk`) and which has no `main` branch locally or on `origin`, every run creates a new top-level thread whose only run fails at workspace preparation with `git worktree add failed`. The scheduler records each of these runs as `lastRunStatus: "succeeded"`, so the task stays enabled and keeps producing failed threads without any signal in the task list or to the scheduling agent.

## Steps to reproduce

Known case, not executed end-to-end against a running T3 server:

1. Open a Git project whose only branch is `master`, with an `origin` remote that also has only `master`.
2. From an agent thread in that project, call `schedule_task` with `{"title":"Hourly check","prompt":"Report repository status.","schedule":{"type":"interval","everyMs":3600000},"bindToCurrentThread":false}`.
3. Call `run_scheduled_task_now` with the returned task id, or wait for the next interval.
4. Open the thread created by that run, then call `list_scheduled_tasks`.

The git commands the launch path issues for step 3 were executed in a disposable repository with the same shape (bare `origin` containing only `master`, plus a clone):

```shell
$ git remote get-url origin                                   # exit 0
$ git fetch --quiet origin '+refs/heads/main:refs/remotes/origin/main'
fatal: couldn't find remote ref refs/heads/main               # exit 128; code then falls back to `git fetch origin` (exit 0)
$ git show-ref --verify --quiet refs/remotes/origin/main      # exit 1, so the local baseRef "main" is used
$ git -c checkout.workers=0 worktree add -b t3code/abc123 ../wt/t3code-abc123 main
fatal: invalid reference: main                                # exit 128
$ git symbolic-ref refs/remotes/origin/HEAD
refs/remotes/origin/master
$ git -c checkout.workers=0 worktree add -b t3code/ok ../wt/t3code-ok master
Preparing worktree (new branch 't3code/ok')                   # control, exit 0
```

A repository with only `trunk` and no remote fails the same `worktree add ... main` with `fatal: invalid reference: main`.

## Expected behavior

The `schedule_task` tool description promises that an unbound task launches a working fresh thread per run: "use false only when the user wants a fresh top-level thread per run. Elsewhere each run launches a fresh thread." The tool exposes no base-branch parameter and says nothing about a `main` branch being required, so a fresh-thread run in a valid Git project with a different default branch should start its agent in a usable workspace.

## Actual behavior

By static trace of the supported entry point: `schedule_task` stores `{ type: "worktree", baseRef: "main", startFromOrigin: true }` for every unbound task, and `update_scheduled_task` re-applies the same value whenever `bindToCurrentThread` is passed. Each run calls `ThreadLaunchService.launch`, which returns after dispatching thread creation and forking workspace preparation into the background. Preparation finds no `origin/main`, keeps the literal local `main`, and runs `git worktree add -b t3code/<id> <path> main`, which fails as shown above. The thread receives a "Workspace preparation failed" item ending in `git worktree add failed` (stderr is dropped; see #4380). Because `launch` already returned, the scheduler's `Effect.exit` is a success, and it records `lastRunStatus: "succeeded"` with `lastRunError: null`, re-arms `nextRunAt`, and leaves the task enabled. The Settings badge and the `list_scheduled_tasks` summary both show success. Runtime reproduction through a running T3 server was not executed.

## Evidence

- Expected source: `schedule_task` tool description in [apps/server/src/mcp/toolkits/orchestrator/tools.ts:100](https://github.com/pingdotgg/t3code/blob/6f9cea00ae967f38fa3cdc9c07f92031806f4264/apps/server/src/mcp/toolkits/orchestrator/tools.ts#L100).
- Failure source: hard-coded strategy in [apps/server/src/mcp/OrchestratorMcpService.ts:196-201](https://github.com/pingdotgg/t3code/blob/6f9cea00ae967f38fa3cdc9c07f92031806f4264/apps/server/src/mcp/OrchestratorMcpService.ts#L196) used at L1348 (create) and L1420 (update); background preparation fork in [apps/server/src/orchestration-v2/ThreadLaunchService.ts:680](https://github.com/pingdotgg/t3code/blob/6f9cea00ae967f38fa3cdc9c07f92031806f4264/apps/server/src/orchestration-v2/ThreadLaunchService.ts#L680) with the origin fallback at L333-367; scheduler status at [apps/server/src/scheduledTasks/ScheduledTaskService.ts:557](https://github.com/pingdotgg/t3code/blob/6f9cea00ae967f38fa3cdc9c07f92031806f4264/apps/server/src/scheduledTasks/ScheduledTaskService.ts#L557); the git transcript above.
- Evidence provenance: static
- Local verification: static-proof
- Reproduction completeness: complete

Runtime reproduction through T3 was not run. The static proof covers the reachable path from the MCP tool: the default at `OrchestratorMcpService.ts:1322-1324` makes any other-project `projectId` unbound; `ScheduledTaskService.upsert` stores the strategy without validation; `GitVcsDriverCore.fetchRemote` falls back to a plain fetch on a missing remote ref; `remoteBranchExists` is false; and `createWorktree` passes the unresolved `main` to `git worktree add`. The git commands were executed directly and produced the failures shown. The Settings UI scheduling path also defaults its base to `main` (`apps/web/src/components/settings/ScheduledTasksSettings.tsx:595`) but lets the user pick another branch, so it is not a clean control. `GitVcsDriverCore.resolveDefaultBranchName` already exists in the server.

## Restoration check

Using the reproduction above (a `master`-only project with a `master`-only `origin`), an unbound scheduled run currently yields a thread whose run failed at workspace preparation with `git worktree add failed`, while the task reports `lastRunStatus: "succeeded"`. After the fix, the same run starts its agent in a prepared worktree (in this fixture, from `master`). Control: a project that does have `main` must still prepare its fresh-thread runs from `main`, and bound tasks (`bindToCurrentThread: true`) must keep posting into the calling thread without a worktree.

## Additional context

The scheduler records whether a run was dispatched, not whether its thread was prepared, for both bound and unbound runs; no contract text defines `lastRunStatus`, so this report treats the misleading "succeeded" status only as a consequence that hides the failure, not as a separate defect. Workarounds available today: use `bindToCurrentThread: true` (runs share the calling thread's context and checkout and queue behind its activity), or edit the task in Settings, Scheduled tasks, and pick a real base branch (a later `update_scheduled_task` that passes `bindToCurrentThread` resets the base to `main`). A non-Git project with an unbound schedule also fails every run at the same preparation step, with "Failed to resolve the VCS driver for this Git command."; that may share the restoration but was not traced further. Related: #4380 (git stderr discarded), discussion #6866 (configurable default base branch for new worktree threads).

## Area

apps/server

## Impact

Major degradation or frequent failure. Every fresh-thread run of an MCP-created schedule fails in Git projects without a `main` branch, and the scheduler records each run as succeeded, so failed threads accumulate unnoticed.

## Version or commit

`main` at `6f9cea00ae967f38fa3cdc9c07f92031806f4264`.

## Environment

Source inspection of `pingdotgg/t3code` `main` at 6f9cea00ae967f38fa3cdc9c07f92031806f4264. Git behavior checked with git 2.56.0 on macOS with global and system config disabled. The T3 server itself was not run.

## Workaround

Editing the task's base branch in Settings, or binding runs to the calling thread, avoids the failure today, at the cost of manual per-task setup or giving up per-run fresh threads.

## Before submitting

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

