Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/server/src/git/GitWorkflowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export class GitWorkflowService extends Context.Service<
readonly cwd: string;
readonly remoteName: string;
}) => Effect.Effect<void, GitCommandError>;
readonly remoteExists: (input: {
readonly cwd: string;
readonly remoteName: string;
}) => Effect.Effect<boolean, GitCommandError>;
readonly resolveRemoteTrackingCommit: (input: {
readonly cwd: string;
readonly refName: string;
Expand Down Expand Up @@ -303,6 +307,10 @@ export const make = Effect.gen(function* () {
ensureGitCommand("GitWorkflowService.fetchRemote", input.cwd).pipe(
Effect.andThen(git.fetchRemote(input)),
),
remoteExists: (input) =>
ensureGitCommand("GitWorkflowService.remoteExists", input.cwd).pipe(
Effect.andThen(git.remoteExists(input)),
),
resolveRemoteTrackingCommit: (input) =>
ensureGitCommand("GitWorkflowService.resolveRemoteTrackingCommit", input.cwd).pipe(
Effect.andThen(git.resolveRemoteTrackingCommit(input)),
Expand Down
113 changes: 113 additions & 0 deletions apps/server/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7134,6 +7134,13 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
pr: null,
}),
);
const remoteExists = vi.fn(
(_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteExists"]>[0]) =>
Effect.sync(() => {
bootstrapGitOperations.push("remote-exists");
return true;
}),
);
const fetchRemote = vi.fn(
(_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"]>[0]) =>
Effect.sync(() => {
Expand Down Expand Up @@ -7181,6 +7188,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
yield* buildAppUnderTest({
layers: {
gitVcsDriver: {
remoteExists,
fetchRemote,
resolveRemoteTrackingCommit,
createWorktree,
Expand Down Expand Up @@ -7271,6 +7279,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
fallbackRemoteName: "origin",
});
assert.deepEqual(bootstrapGitOperations, [
"remote-exists",
"fetch",
"resolve-remote-commit",
"create-worktree",
Expand Down Expand Up @@ -7299,6 +7308,110 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
}).pipe(Effect.provide(NodeHttpServer.layerTest)),
);

it.effect(
"falls back to the local base branch when startFromOrigin is set but no origin remote exists",
() =>
Effect.gen(function* () {
const dispatchedCommands: Array<OrchestrationCommand> = [];
const remoteExists = vi.fn(
(_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteExists"]>[0]) =>
Effect.succeed(false),
);
const fetchRemote = vi.fn(
(_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"]>[0]) => Effect.void,
);
const resolveRemoteTrackingCommit = vi.fn(
(_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) =>
Effect.succeed({
commitSha: "0123456789abcdef0123456789abcdef01234567",
remoteRefName: "origin/main",
}),
);
const createWorktree = vi.fn(
(_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) =>
Effect.succeed({
worktree: {
refName: "t3code/bootstrap-refName",
path: "/tmp/bootstrap-worktree",
},
}),
);

yield* buildAppUnderTest({
layers: {
gitVcsDriver: {
remoteExists,
fetchRemote,
resolveRemoteTrackingCommit,
createWorktree,
},
orchestrationEngine: {
dispatch: (command) =>
Effect.sync(() => {
dispatchedCommands.push(command);
return { sequence: dispatchedCommands.length };
}),
readEvents: () => Stream.empty,
},
},
});

const createdAt = "2026-01-01T00:00:00.000Z";
const wsUrl = yield* getWsServerUrl("/ws");
yield* Effect.scoped(
withWsRpcClient(wsUrl, (client) =>
client[ORCHESTRATION_WS_METHODS.dispatchCommand]({
type: "thread.turn.start",
commandId: CommandId.make("cmd-bootstrap-turn-start-no-origin"),
threadId: ThreadId.make("thread-bootstrap-no-origin"),
message: {
messageId: MessageId.make("msg-bootstrap-no-origin"),
role: "user",
text: "hello",
attachments: [],
},
modelSelection: defaultModelSelection,
runtimeMode: "full-access",
interactionMode: "default",
bootstrap: {
createThread: {
projectId: defaultProjectId,
title: "Bootstrap Thread",
modelSelection: defaultModelSelection,
runtimeMode: "full-access",
interactionMode: "default",
branch: "main",
worktreePath: null,
createdAt,
},
prepareWorktree: {
projectCwd: "/tmp/project",
baseBranch: "main",
branch: "t3code/bootstrap-refName",
startFromOrigin: true,
},
},
createdAt,
}),
),
);

assert.deepEqual(remoteExists.mock.calls[0]?.[0], {
cwd: "/tmp/project",
remoteName: "origin",
});
assert.equal(fetchRemote.mock.calls.length, 0);
assert.equal(resolveRemoteTrackingCommit.mock.calls.length, 0);
assert.deepEqual(createWorktree.mock.calls[0]?.[0], {
cwd: "/tmp/project",
refName: "main",
newRefName: "t3code/bootstrap-refName",
baseRefName: "main",
path: null,
});
}).pipe(Effect.provide(NodeHttpServer.layerTest)),
);

it.effect("records setup-script failures without aborting bootstrap turn start", () =>
Effect.gen(function* () {
const dispatchedCommands: Array<OrchestrationCommand> = [];
Expand Down
6 changes: 6 additions & 0 deletions apps/server/src/vcs/GitVcsDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ export interface GitFetchRemoteInput {
remoteName: string;
}

export interface GitRemoteExistsInput {
cwd: string;
remoteName: string;
}

export interface GitResolveRemoteTrackingCommitInput {
cwd: string;
refName: string;
Expand Down Expand Up @@ -243,6 +248,7 @@ export class GitVcsDriver extends Context.Service<
readonly ensureRemote: (input: GitEnsureRemoteInput) => Effect.Effect<string, GitCommandError>;
readonly resolvePrimaryRemoteName: (cwd: string) => Effect.Effect<string, GitCommandError>;
readonly fetchRemote: (input: GitFetchRemoteInput) => Effect.Effect<void, GitCommandError>;
readonly remoteExists: (input: GitRemoteExistsInput) => Effect.Effect<boolean, GitCommandError>;
readonly resolveRemoteTrackingCommit: (
input: GitResolveRemoteTrackingCommitInput,
) => Effect.Effect<GitResolveRemoteTrackingCommitResult, GitCommandError>;
Expand Down
8 changes: 6 additions & 2 deletions apps/server/src/vcs/GitVcsDriverCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1284,11 +1284,14 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
},
).pipe(Effect.map((result) => result.exitCode === 0));

const originRemoteExists = (cwd: string): Effect.Effect<boolean, GitCommandError> =>
executeGit("GitVcsDriver.originRemoteExists", cwd, ["remote", "get-url", "origin"], {
const remoteExists: GitVcsDriver.GitVcsDriver["Service"]["remoteExists"] = (input) =>
executeGit("GitVcsDriver.remoteExists", input.cwd, ["remote", "get-url", input.remoteName], {
allowNonZeroExit: true,
}).pipe(Effect.map((result) => result.exitCode === 0));

const originRemoteExists = (cwd: string): Effect.Effect<boolean, GitCommandError> =>
remoteExists({ cwd, remoteName: "origin" });

const listRemoteNames = (cwd: string): Effect.Effect<ReadonlyArray<string>, GitCommandError> =>
runGitStdout("GitVcsDriver.listRemoteNames", cwd, ["remote"]).pipe(
Effect.map(parseRemoteNamesInGitOrder),
Expand Down Expand Up @@ -3071,6 +3074,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
ensureRemote: (input) => withListRefsInvalidation(input.cwd, ensureRemote(input)),
resolvePrimaryRemoteName,
fetchRemote: (input) => withListRefsInvalidation(input.cwd, fetchRemote(input)),
remoteExists,
resolveRemoteTrackingCommit,
fetchRemoteBranch: (input) => withListRefsInvalidation(input.cwd, fetchRemoteBranch(input)),
fetchRemoteTrackingBranch: (input) =>
Expand Down
11 changes: 10 additions & 1 deletion apps/server/src/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,16 @@ const makeWsRpcLayer = (

if (bootstrap?.prepareWorktree) {
let worktreeBaseRef = bootstrap.prepareWorktree.baseBranch;
if (bootstrap.prepareWorktree.startFromOrigin) {
// "Start from origin" is a stored default; repos without an
// origin remote fall back to the local base branch instead of
// failing the whole bootstrap on `git fetch origin`.
const startFromOrigin =
bootstrap.prepareWorktree.startFromOrigin === true &&
(yield* gitWorkflow.remoteExists({
cwd: bootstrap.prepareWorktree.projectCwd,
remoteName: "origin",
}));
if (startFromOrigin) {
yield* gitWorkflow.fetchRemote({
cwd: bootstrap.prepareWorktree.projectCwd,
remoteName: "origin",
Expand Down
Loading