authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-08-06 20:13:10-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-06 20:13:10-04:00
log331c6dce7f6745863752b1b423fc76d24014deec
tree47b28933c5e42cc60f431c8f1df40792402ed205
parent4f5834ba72c5905a318c00456dd21271b2fa9d6f
signature Signed by PGP key B5690EEEBB952194

fix(server): skip origin fetch when creating worktrees in repos without an origin remote (#5556)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

5 files changed, 143 insertions(+), 3 deletions(-)

apps/server/src/git/GitWorkflowService.ts+8
......@@ -69,6 +69,10 @@ export class GitWorkflowService extends Context.Service<
6969 readonly cwd: string;
7070 readonly remoteName: string;
7171 }) => Effect.Effect<void, GitCommandError>;
72 readonly remoteExists: (input: {
73 readonly cwd: string;
74 readonly remoteName: string;
75 }) => Effect.Effect<boolean, GitCommandError>;
7276 readonly resolveRemoteTrackingCommit: (input: {
7377 readonly cwd: string;
7478 readonly refName: string;
......@@ -303,6 +307,10 @@ export const make = Effect.gen(function* () {
303307 ensureGitCommand("GitWorkflowService.fetchRemote", input.cwd).pipe(
304308 Effect.andThen(git.fetchRemote(input)),
305309 ),
310 remoteExists: (input) =>
311 ensureGitCommand("GitWorkflowService.remoteExists", input.cwd).pipe(
312 Effect.andThen(git.remoteExists(input)),
313 ),
306314 resolveRemoteTrackingCommit: (input) =>
307315 ensureGitCommand("GitWorkflowService.resolveRemoteTrackingCommit", input.cwd).pipe(
308316 Effect.andThen(git.resolveRemoteTrackingCommit(input)),
apps/server/src/server.test.ts+113
......@@ -7134,6 +7134,13 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
71347134 pr: null,
71357135 }),
71367136 );
7137 const remoteExists = vi.fn(
7138 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteExists"]>[0]) =>
7139 Effect.sync(() => {
7140 bootstrapGitOperations.push("remote-exists");
7141 return true;
7142 }),
7143 );
71377144 const fetchRemote = vi.fn(
71387145 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"]>[0]) =>
71397146 Effect.sync(() => {
......@@ -7181,6 +7188,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
71817188 yield* buildAppUnderTest({
71827189 layers: {
71837190 gitVcsDriver: {
7191 remoteExists,
71847192 fetchRemote,
71857193 resolveRemoteTrackingCommit,
71867194 createWorktree,
......@@ -7271,6 +7279,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
72717279 fallbackRemoteName: "origin",
72727280 });
72737281 assert.deepEqual(bootstrapGitOperations, [
7282 "remote-exists",
72747283 "fetch",
72757284 "resolve-remote-commit",
72767285 "create-worktree",
......@@ -7299,6 +7308,110 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
72997308 }).pipe(Effect.provide(NodeHttpServer.layerTest)),
73007309 );
73017310
7311 it.effect(
7312 "falls back to the local base branch when startFromOrigin is set but no origin remote exists",
7313 () =>
7314 Effect.gen(function* () {
7315 const dispatchedCommands: Array<OrchestrationCommand> = [];
7316 const remoteExists = vi.fn(
7317 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteExists"]>[0]) =>
7318 Effect.succeed(false),
7319 );
7320 const fetchRemote = vi.fn(
7321 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"]>[0]) => Effect.void,
7322 );
7323 const resolveRemoteTrackingCommit = vi.fn(
7324 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) =>
7325 Effect.succeed({
7326 commitSha: "0123456789abcdef0123456789abcdef01234567",
7327 remoteRefName: "origin/main",
7328 }),
7329 );
7330 const createWorktree = vi.fn(
7331 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) =>
7332 Effect.succeed({
7333 worktree: {
7334 refName: "t3code/bootstrap-refName",
7335 path: "/tmp/bootstrap-worktree",
7336 },
7337 }),
7338 );
7339
7340 yield* buildAppUnderTest({
7341 layers: {
7342 gitVcsDriver: {
7343 remoteExists,
7344 fetchRemote,
7345 resolveRemoteTrackingCommit,
7346 createWorktree,
7347 },
7348 orchestrationEngine: {
7349 dispatch: (command) =>
7350 Effect.sync(() => {
7351 dispatchedCommands.push(command);
7352 return { sequence: dispatchedCommands.length };
7353 }),
7354 readEvents: () => Stream.empty,
7355 },
7356 },
7357 });
7358
7359 const createdAt = "2026-01-01T00:00:00.000Z";
7360 const wsUrl = yield* getWsServerUrl("/ws");
7361 yield* Effect.scoped(
7362 withWsRpcClient(wsUrl, (client) =>
7363 client[ORCHESTRATION_WS_METHODS.dispatchCommand]({
7364 type: "thread.turn.start",
7365 commandId: CommandId.make("cmd-bootstrap-turn-start-no-origin"),
7366 threadId: ThreadId.make("thread-bootstrap-no-origin"),
7367 message: {
7368 messageId: MessageId.make("msg-bootstrap-no-origin"),
7369 role: "user",
7370 text: "hello",
7371 attachments: [],
7372 },
7373 modelSelection: defaultModelSelection,
7374 runtimeMode: "full-access",
7375 interactionMode: "default",
7376 bootstrap: {
7377 createThread: {
7378 projectId: defaultProjectId,
7379 title: "Bootstrap Thread",
7380 modelSelection: defaultModelSelection,
7381 runtimeMode: "full-access",
7382 interactionMode: "default",
7383 branch: "main",
7384 worktreePath: null,
7385 createdAt,
7386 },
7387 prepareWorktree: {
7388 projectCwd: "/tmp/project",
7389 baseBranch: "main",
7390 branch: "t3code/bootstrap-refName",
7391 startFromOrigin: true,
7392 },
7393 },
7394 createdAt,
7395 }),
7396 ),
7397 );
7398
7399 assert.deepEqual(remoteExists.mock.calls[0]?.[0], {
7400 cwd: "/tmp/project",
7401 remoteName: "origin",
7402 });
7403 assert.equal(fetchRemote.mock.calls.length, 0);
7404 assert.equal(resolveRemoteTrackingCommit.mock.calls.length, 0);
7405 assert.deepEqual(createWorktree.mock.calls[0]?.[0], {
7406 cwd: "/tmp/project",
7407 refName: "main",
7408 newRefName: "t3code/bootstrap-refName",
7409 baseRefName: "main",
7410 path: null,
7411 });
7412 }).pipe(Effect.provide(NodeHttpServer.layerTest)),
7413 );
7414
73027415 it.effect("records setup-script failures without aborting bootstrap turn start", () =>
73037416 Effect.gen(function* () {
73047417 const dispatchedCommands: Array<OrchestrationCommand> = [];
apps/server/src/vcs/GitVcsDriver.ts+6
......@@ -168,6 +168,11 @@ export interface GitFetchRemoteInput {
168168 remoteName: string;
169169}
170170
171export interface GitRemoteExistsInput {
172 cwd: string;
173 remoteName: string;
174}
175
171176export interface GitResolveRemoteTrackingCommitInput {
172177 cwd: string;
173178 refName: string;
......@@ -243,6 +248,7 @@ export class GitVcsDriver extends Context.Service<
243248 readonly ensureRemote: (input: GitEnsureRemoteInput) => Effect.Effect<string, GitCommandError>;
244249 readonly resolvePrimaryRemoteName: (cwd: string) => Effect.Effect<string, GitCommandError>;
245250 readonly fetchRemote: (input: GitFetchRemoteInput) => Effect.Effect<void, GitCommandError>;
251 readonly remoteExists: (input: GitRemoteExistsInput) => Effect.Effect<boolean, GitCommandError>;
246252 readonly resolveRemoteTrackingCommit: (
247253 input: GitResolveRemoteTrackingCommitInput,
248254 ) => Effect.Effect<GitResolveRemoteTrackingCommitResult, GitCommandError>;
apps/server/src/vcs/GitVcsDriverCore.ts+6-2
......@@ -1284,11 +1284,14 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
12841284 },
12851285 ).pipe(Effect.map((result) => result.exitCode === 0));
12861286
1287 const originRemoteExists = (cwd: string): Effect.Effect<boolean, GitCommandError> =>
1288 executeGit("GitVcsDriver.originRemoteExists", cwd, ["remote", "get-url", "origin"], {
1287 const remoteExists: GitVcsDriver.GitVcsDriver["Service"]["remoteExists"] = (input) =>
1288 executeGit("GitVcsDriver.remoteExists", input.cwd, ["remote", "get-url", input.remoteName], {
12891289 allowNonZeroExit: true,
12901290 }).pipe(Effect.map((result) => result.exitCode === 0));
12911291
1292 const originRemoteExists = (cwd: string): Effect.Effect<boolean, GitCommandError> =>
1293 remoteExists({ cwd, remoteName: "origin" });
1294
12921295 const listRemoteNames = (cwd: string): Effect.Effect<ReadonlyArray<string>, GitCommandError> =>
12931296 runGitStdout("GitVcsDriver.listRemoteNames", cwd, ["remote"]).pipe(
12941297 Effect.map(parseRemoteNamesInGitOrder),
......@@ -3071,6 +3074,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
30713074 ensureRemote: (input) => withListRefsInvalidation(input.cwd, ensureRemote(input)),
30723075 resolvePrimaryRemoteName,
30733076 fetchRemote: (input) => withListRefsInvalidation(input.cwd, fetchRemote(input)),
3077 remoteExists,
30743078 resolveRemoteTrackingCommit,
30753079 fetchRemoteBranch: (input) => withListRefsInvalidation(input.cwd, fetchRemoteBranch(input)),
30763080 fetchRemoteTrackingBranch: (input) =>
apps/server/src/ws.ts+10-1
......@@ -908,7 +908,16 @@ const makeWsRpcLayer = (
908908
909909 if (bootstrap?.prepareWorktree) {
910910 let worktreeBaseRef = bootstrap.prepareWorktree.baseBranch;
911 if (bootstrap.prepareWorktree.startFromOrigin) {
911 // "Start from origin" is a stored default; repos without an
912 // origin remote fall back to the local base branch instead of
913 // failing the whole bootstrap on `git fetch origin`.
914 const startFromOrigin =
915 bootstrap.prepareWorktree.startFromOrigin === true &&
916 (yield* gitWorkflow.remoteExists({
917 cwd: bootstrap.prepareWorktree.projectCwd,
918 remoteName: "origin",
919 }));
920 if (startFromOrigin) {
912921 yield* gitWorkflow.fetchRemote({
913922 cwd: bootstrap.prepareWorktree.projectCwd,
914923 remoteName: "origin",