authorgravatar for 153842990+yashranaway@users.noreply.github.comAditya Garud <153842990+yashranaway@users.noreply.github.com> 2026-09-02 13:14:14+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 00:44:14-07:00
loga81a52afbb4e03ba82b2743801772151ed8c7d70
treea8592f61ceedce664445b6f5c7a3b74a40e9bdd7
parenta56b0cd7178dfef299aa49b26773a80a34abe205
signature Signed by PGP key B5690EEEBB952194

fix(server): allow local-only worktree bases (#8751)


6 files changed, 179 insertions(+), 109 deletions(-)

apps/server/src/git/GitWorkflowService.ts+9
......@@ -73,6 +73,11 @@ export class GitWorkflowService extends Context.Service<
7373 readonly cwd: string;
7474 readonly remoteName: string;
7575 }) => Effect.Effect<boolean, GitCommandError>;
76 readonly remoteBranchExists: (input: {
77 readonly cwd: string;
78 readonly remoteName: string;
79 readonly refName: string;
80 }) => Effect.Effect<boolean, GitCommandError>;
7681 readonly resolveRemoteTrackingCommit: (input: {
7782 readonly cwd: string;
7883 readonly refName: string;
......@@ -314,6 +319,10 @@ export const make = Effect.gen(function* () {
314319 ensureGitCommand("GitWorkflowService.remoteExists", input.cwd).pipe(
315320 Effect.andThen(git.remoteExists(input)),
316321 ),
322 remoteBranchExists: (input) =>
323 ensureGitCommand("GitWorkflowService.remoteBranchExists", input.cwd).pipe(
324 Effect.andThen(git.remoteBranchExists(input)),
325 ),
317326 resolveRemoteTrackingCommit: (input) =>
318327 ensureGitCommand("GitWorkflowService.resolveRemoteTrackingCommit", input.cwd).pipe(
319328 Effect.andThen(git.resolveRemoteTrackingCommit(input)),
apps/server/src/server.test.ts+120-92
......@@ -8378,6 +8378,13 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
83788378 bootstrapGitOperations.push("fetch");
83798379 }),
83808380 );
8381 const remoteBranchExists = vi.fn(
8382 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteBranchExists"]>[0]) =>
8383 Effect.sync(() => {
8384 bootstrapGitOperations.push("remote-branch-exists");
8385 return true;
8386 }),
8387 );
83818388 const fetchedOriginCommit = "0123456789abcdef0123456789abcdef01234567";
83828389 const resolveRemoteTrackingCommit = vi.fn(
83838390 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) =>
......@@ -8421,6 +8428,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
84218428 gitVcsDriver: {
84228429 remoteExists,
84238430 fetchRemote,
8431 remoteBranchExists,
84248432 resolveRemoteTrackingCommit,
84258433 createWorktree,
84268434 },
......@@ -8504,6 +8512,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
85048512 cwd: "/tmp/project",
85058513 remoteName: "origin",
85068514 });
8515 assert.deepEqual(remoteBranchExists.mock.calls[0]?.[0], {
8516 cwd: "/tmp/project",
8517 remoteName: "origin",
8518 refName: "main",
8519 });
85078520 assert.deepEqual(resolveRemoteTrackingCommit.mock.calls[0]?.[0], {
85088521 cwd: "/tmp/project",
85098522 refName: "main",
......@@ -8512,6 +8525,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
85128525 assert.deepEqual(bootstrapGitOperations, [
85138526 "remote-exists",
85148527 "fetch",
8528 "remote-branch-exists",
85158529 "resolve-remote-commit",
85168530 "create-worktree",
85178531 ]);
......@@ -8539,108 +8553,122 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
85398553 }).pipe(Effect.provide(NodeHttpServer.layerTest)),
85408554 );
85418555
8542 it.effect(
8543 "falls back to the local base branch when startFromOrigin is set but no origin remote exists",
8544 () =>
8545 Effect.gen(function* () {
8546 const dispatchedCommands: Array<OrchestrationCommand> = [];
8547 const remoteExists = vi.fn(
8548 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteExists"]>[0]) =>
8549 Effect.succeed(false),
8550 );
8551 const fetchRemote = vi.fn(
8552 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"]>[0]) => Effect.void,
8553 );
8554 const resolveRemoteTrackingCommit = vi.fn(
8555 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) =>
8556 Effect.succeed({
8557 commitSha: "0123456789abcdef0123456789abcdef01234567",
8558 remoteRefName: "origin/main",
8559 }),
8560 );
8561 const createWorktree = vi.fn(
8562 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) =>
8563 Effect.succeed({
8564 worktree: {
8565 refName: "t3code/bootstrap-refName",
8566 path: "/tmp/bootstrap-worktree",
8567 },
8568 }),
8569 );
8570
8571 yield* buildAppUnderTest({
8572 layers: {
8573 gitVcsDriver: {
8574 remoteExists,
8575 fetchRemote,
8576 resolveRemoteTrackingCommit,
8577 createWorktree,
8578 },
8579 orchestrationEngine: {
8580 dispatch: (command) =>
8581 Effect.sync(() => {
8582 dispatchedCommands.push(command);
8583 return { sequence: dispatchedCommands.length };
8584 }),
8585 readEvents: () => Stream.empty,
8556 it.effect.each([
8557 { caseName: "the origin remote is missing", hasOrigin: false },
8558 { caseName: "the base branch exists only locally", hasOrigin: true },
8559 ])("falls back to the local base branch when $caseName", ({ hasOrigin }) =>
8560 Effect.gen(function* () {
8561 const dispatchedCommands: Array<OrchestrationCommand> = [];
8562 const remoteExists = vi.fn(
8563 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteExists"]>[0]) =>
8564 Effect.succeed(hasOrigin),
8565 );
8566 const fetchRemote = vi.fn(
8567 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"]>[0]) => Effect.void,
8568 );
8569 const resolveRemoteTrackingCommit = vi.fn(
8570 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) =>
8571 Effect.succeed({
8572 commitSha: "0123456789abcdef0123456789abcdef01234567",
8573 remoteRefName: "origin/main",
8574 }),
8575 );
8576 const remoteBranchExists = vi.fn(
8577 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteBranchExists"]>[0]) =>
8578 Effect.succeed(false),
8579 );
8580 const createWorktree = vi.fn(
8581 (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) =>
8582 Effect.succeed({
8583 worktree: {
8584 refName: "t3code/bootstrap-refName",
8585 path: "/tmp/bootstrap-worktree",
85868586 },
8587 }),
8588 );
8589
8590 yield* buildAppUnderTest({
8591 layers: {
8592 gitVcsDriver: {
8593 remoteExists,
8594 fetchRemote,
8595 remoteBranchExists,
8596 resolveRemoteTrackingCommit,
8597 createWorktree,
85878598 },
8588 });
8599 orchestrationEngine: {
8600 dispatch: (command) =>
8601 Effect.sync(() => {
8602 dispatchedCommands.push(command);
8603 return { sequence: dispatchedCommands.length };
8604 }),
8605 readEvents: () => Stream.empty,
8606 },
8607 },
8608 });
85898609
8590 const createdAt = "2026-01-01T00:00:00.000Z";
8591 const wsUrl = yield* getWsServerUrl("/ws");
8592 yield* Effect.scoped(
8593 withWsRpcClient(wsUrl, (client) =>
8594 client[ORCHESTRATION_WS_METHODS.dispatchCommand]({
8595 type: "thread.turn.start",
8596 commandId: CommandId.make("cmd-bootstrap-turn-start-no-origin"),
8597 threadId: ThreadId.make("thread-bootstrap-no-origin"),
8598 message: {
8599 messageId: MessageId.make("msg-bootstrap-no-origin"),
8600 role: "user",
8601 text: "hello",
8602 attachments: [],
8610 const createdAt = "2026-01-01T00:00:00.000Z";
8611 const wsUrl = yield* getWsServerUrl("/ws");
8612 yield* Effect.scoped(
8613 withWsRpcClient(wsUrl, (client) =>
8614 client[ORCHESTRATION_WS_METHODS.dispatchCommand]({
8615 type: "thread.turn.start",
8616 commandId: CommandId.make("cmd-bootstrap-turn-start-no-origin"),
8617 threadId: ThreadId.make("thread-bootstrap-no-origin"),
8618 message: {
8619 messageId: MessageId.make("msg-bootstrap-no-origin"),
8620 role: "user",
8621 text: "hello",
8622 attachments: [],
8623 },
8624 modelSelection: defaultModelSelection,
8625 runtimeMode: "full-access",
8626 interactionMode: "default",
8627 bootstrap: {
8628 createThread: {
8629 projectId: defaultProjectId,
8630 title: "Bootstrap Thread",
8631 modelSelection: defaultModelSelection,
8632 runtimeMode: "full-access",
8633 interactionMode: "default",
8634 branch: "main",
8635 worktreePath: null,
8636 createdAt,
86038637 },
8604 modelSelection: defaultModelSelection,
8605 runtimeMode: "full-access",
8606 interactionMode: "default",
8607 bootstrap: {
8608 createThread: {
8609 projectId: defaultProjectId,
8610 title: "Bootstrap Thread",
8611 modelSelection: defaultModelSelection,
8612 runtimeMode: "full-access",
8613 interactionMode: "default",
8614 branch: "main",
8615 worktreePath: null,
8616 createdAt,
8617 },
8618 prepareWorktree: {
8619 projectCwd: "/tmp/project",
8620 baseBranch: "main",
8621 branch: "t3code/bootstrap-refName",
8622 startFromOrigin: true,
8623 },
8638 prepareWorktree: {
8639 projectCwd: "/tmp/project",
8640 baseBranch: "main",
8641 branch: "t3code/bootstrap-refName",
8642 startFromOrigin: true,
86248643 },
8625 createdAt,
8626 }),
8627 ),
8628 );
8644 },
8645 createdAt,
8646 }),
8647 ),
8648 );
86298649
8630 assert.deepEqual(remoteExists.mock.calls[0]?.[0], {
8650 assert.deepEqual(remoteExists.mock.calls[0]?.[0], {
8651 cwd: "/tmp/project",
8652 remoteName: "origin",
8653 });
8654 assert.equal(fetchRemote.mock.calls.length, hasOrigin ? 1 : 0);
8655 assert.equal(remoteBranchExists.mock.calls.length, hasOrigin ? 1 : 0);
8656 if (hasOrigin) {
8657 assert.deepEqual(remoteBranchExists.mock.calls[0]?.[0], {
86318658 cwd: "/tmp/project",
86328659 remoteName: "origin",
8633 });
8634 assert.equal(fetchRemote.mock.calls.length, 0);
8635 assert.equal(resolveRemoteTrackingCommit.mock.calls.length, 0);
8636 assert.deepEqual(createWorktree.mock.calls[0]?.[0], {
8637 cwd: "/tmp/project",
86388660 refName: "main",
8639 newRefName: "t3code/bootstrap-refName",
8640 baseRefName: "main",
8641 path: null,
86428661 });
8643 }).pipe(Effect.provide(NodeHttpServer.layerTest)),
8662 }
8663 assert.equal(resolveRemoteTrackingCommit.mock.calls.length, 0);
8664 assert.deepEqual(createWorktree.mock.calls[0]?.[0], {
8665 cwd: "/tmp/project",
8666 refName: "main",
8667 newRefName: "t3code/bootstrap-refName",
8668 baseRefName: "main",
8669 path: null,
8670 });
8671 }).pipe(Effect.provide(NodeHttpServer.layerTest)),
86448672 );
86458673
86468674 it.effect("records setup-script failures without aborting bootstrap turn start", () =>
apps/server/src/vcs/GitVcsDriver.ts+7
......@@ -204,6 +204,10 @@ export interface GitRemoteExistsInput {
204204 remoteName: string;
205205}
206206
207export interface GitRemoteBranchExistsInput extends GitRemoteExistsInput {
208 refName: string;
209}
210
207211export interface GitResolveRemoteTrackingCommitInput {
208212 cwd: string;
209213 refName: string;
......@@ -295,6 +299,9 @@ export class GitVcsDriver extends Context.Service<
295299 ) => Effect.Effect<string | null, GitCommandError>;
296300 readonly fetchRemote: (input: GitFetchRemoteInput) => Effect.Effect<void, GitCommandError>;
297301 readonly remoteExists: (input: GitRemoteExistsInput) => Effect.Effect<boolean, GitCommandError>;
302 readonly remoteBranchExists: (
303 input: GitRemoteBranchExistsInput,
304 ) => Effect.Effect<boolean, GitCommandError>;
298305 readonly resolveRemoteTrackingCommit: (
299306 input: GitResolveRemoteTrackingCommitInput,
300307 ) => Effect.Effect<GitResolveRemoteTrackingCommitResult, GitCommandError>;
apps/server/src/vcs/GitVcsDriverCore.test.ts+17
......@@ -1665,6 +1665,23 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
16651665 const driver = yield* GitVcsDriver.GitVcsDriver;
16661666 yield* driver.fetchRemote({ cwd, remoteName: "origin" });
16671667
1668 assert.equal(
1669 yield* driver.remoteBranchExists({
1670 cwd,
1671 remoteName: "origin",
1672 refName: initialBranch,
1673 }),
1674 true,
1675 );
1676 assert.equal(
1677 yield* driver.remoteBranchExists({
1678 cwd,
1679 remoteName: "origin",
1680 refName: "local-only",
1681 }),
1682 false,
1683 );
1684
16681685 const resolvedBase = yield* driver.resolveRemoteTrackingCommit({
16691686 cwd,
16701687 refName: initialBranch,
apps/server/src/vcs/GitVcsDriverCore.ts+14-11
......@@ -1292,15 +1292,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
12921292 }),
12931293 );
12941294
1295 const remoteBranchExists = (
1296 cwd: string,
1297 remoteName: string,
1298 refName: string,
1299 ): Effect.Effect<boolean, GitCommandError> =>
1295 const remoteBranchExists: GitVcsDriver.GitVcsDriver["Service"]["remoteBranchExists"] = (input) =>
13001296 executeGit(
13011297 "GitVcsDriver.remoteBranchExists",
1302 cwd,
1303 ["show-ref", "--verify", "--quiet", `refs/remotes/${remoteName}/${refName}`],
1298 input.cwd,
1299 ["show-ref", "--verify", "--quiet", `refs/remotes/${input.remoteName}/${input.refName}`],
13041300 {
13051301 allowNonZeroExit: true,
13061302 },
......@@ -1447,7 +1443,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
14471443
14481444 if (
14491445 primaryRemoteName &&
1450 (yield* remoteBranchExists(cwd, primaryRemoteName, normalizedCandidate))
1446 (yield* remoteBranchExists({
1447 cwd,
1448 remoteName: primaryRemoteName,
1449 refName: normalizedCandidate,
1450 }))
14511451 ) {
14521452 return `${primaryRemoteName}/${normalizedCandidate}`;
14531453 }
......@@ -1965,9 +1965,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
19651965 };
19661966 }
19671967
1968 const hasRemoteBranch = yield* remoteBranchExists(cwd, publishRemoteName, branch).pipe(
1969 Effect.orElseSucceed(() => false),
1970 );
1968 const hasRemoteBranch = yield* remoteBranchExists({
1969 cwd,
1970 remoteName: publishRemoteName,
1971 refName: branch,
1972 }).pipe(Effect.orElseSucceed(() => false));
19711973 if (hasRemoteBranch) {
19721974 return {
19731975 status: "skipped_up_to_date" as const,
......@@ -3315,6 +3317,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
33153317 resolveDefaultBranchName,
33163318 fetchRemote: (input) => withListRefsInvalidation(input.cwd, fetchRemote(input)),
33173319 remoteExists,
3320 remoteBranchExists,
33183321 resolveRemoteTrackingCommit,
33193322 fetchRemoteBranch: (input) => withListRefsInvalidation(input.cwd, fetchRemoteBranch(input)),
33203323 fetchRemoteTrackingBranch: (input) =>
apps/server/src/ws.ts+12-6
......@@ -1094,9 +1094,8 @@ const makeWsRpcLayer = (
10941094
10951095 if (bootstrap?.prepareWorktree) {
10961096 let worktreeBaseRef = bootstrap.prepareWorktree.baseBranch;
1097 // "Start from origin" is a stored default; repos without an
1098 // origin remote fall back to the local base branch instead of
1099 // failing the whole bootstrap on `git fetch origin`.
1097 // "Start from origin" is a stored default; repos without the
1098 // requested remote branch fall back to the local base branch.
11001099 const startFromOrigin =
11011100 bootstrap.prepareWorktree.startFromOrigin === true &&
11021101 (yield* gitWorkflow.remoteExists({
......@@ -1108,12 +1107,19 @@ const makeWsRpcLayer = (
11081107 cwd: bootstrap.prepareWorktree.projectCwd,
11091108 remoteName: "origin",
11101109 });
1111 const resolvedRemoteBase = yield* gitWorkflow.resolveRemoteTrackingCommit({
1110 const remoteBaseExists = yield* gitWorkflow.remoteBranchExists({
11121111 cwd: bootstrap.prepareWorktree.projectCwd,
11131112 refName: bootstrap.prepareWorktree.baseBranch,
1114 fallbackRemoteName: "origin",
1113 remoteName: "origin",
11151114 });
1116 worktreeBaseRef = resolvedRemoteBase.commitSha;
1115 if (remoteBaseExists) {
1116 const resolvedRemoteBase = yield* gitWorkflow.resolveRemoteTrackingCommit({
1117 cwd: bootstrap.prepareWorktree.projectCwd,
1118 refName: bootstrap.prepareWorktree.baseBranch,
1119 fallbackRemoteName: "origin",
1120 });
1121 worktreeBaseRef = resolvedRemoteBase.commitSha;
1122 }
11171123 }
11181124 const worktree = yield* gitWorkflow.createWorktree({
11191125 cwd: bootstrap.prepareWorktree.projectCwd,