Repository navigation
fix(server): run a worktree request against a non-repository project as a root launch #16152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5e69ac6
c5e076f
1ede0f8
ca35da9
831597f
5ac9189
d3bd6eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -743,10 +743,33 @@ const make = Effect.gen(function* () { | |
| yield* validateReusableThread(input, candidateThreadId); | ||
| } | ||
|
|
||
| // Derived from the command id, so a replay is known before anything else runs. | ||
| const messageCommandId = CommandId.make(`${input.commandId}:initial-message`); | ||
| const messageWasAlreadyAccepted = | ||
| input.initialMessage === undefined | ||
| ? false | ||
| : Option.isSome(yield* readReceipt(input, messageCommandId)); | ||
|
|
||
| // A worktree needs a Git repository. The Scratch project is a folder, so a | ||
| // worktree request there becomes a root launch, which the block below | ||
| // answers with a folder of its own. Resolved before the strategy reaches | ||
| // the run, so a retry replays what happened. Only "no VCS at all" degrades: | ||
| // a non-Git repository keeps its unsupported-VCS failure rather than | ||
| // silently sharing the project checkout. A replay detects nothing, since its | ||
| // run already recorded the answer. | ||
| const requestedStrategy: ThreadLaunchWorkspaceStrategy = | ||
| input.workspaceStrategy.type === "worktree" && | ||
| !messageWasAlreadyAccepted && | ||
| !(yield* git | ||
| .isGitRepository(project.workspaceRoot) | ||
| .pipe(Effect.mapError(mapError(input, "provision-worktree", candidateThreadId)))) | ||
| ? { type: "root" } | ||
| : input.workspaceStrategy; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| // A Scratch thread launched at the project root runs in a folder of its | ||
| // own. Only the first attempt claims one; a retry replays its create. | ||
| const workspaceStrategy: ThreadLaunchWorkspaceStrategy = | ||
| input.workspaceStrategy.type === "root" && Option.isNone(launchReceipt) | ||
| requestedStrategy.type === "root" && Option.isNone(launchReceipt) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ? Option.match( | ||
| yield* managedFolders | ||
| .folderForThread({ | ||
|
|
@@ -756,11 +779,11 @@ const make = Effect.gen(function* () { | |
| }) | ||
| .pipe(Effect.mapError(mapError(input, "provision-worktree", candidateThreadId))), | ||
| { | ||
| onNone: () => input.workspaceStrategy, | ||
| onNone: () => requestedStrategy, | ||
| onSome: (worktreePath) => ({ type: "existing_worktree", worktreePath }), | ||
| }, | ||
| ) | ||
| : input.workspaceStrategy; | ||
| : requestedStrategy; | ||
| const initialBranch = workspaceStrategy.branch ?? null; | ||
| const initialWorktreePath = | ||
| workspaceStrategy.type === "existing_worktree" ? workspaceStrategy.worktreePath : null; | ||
|
|
@@ -806,11 +829,7 @@ const make = Effect.gen(function* () { | |
| } | ||
|
|
||
| let runId: RunId | null = null; | ||
| let messageWasAlreadyAccepted = false; | ||
| if (input.initialMessage !== undefined) { | ||
| const messageCommandId = CommandId.make(`${input.commandId}:initial-message`); | ||
| const messageReceipt = yield* readReceipt(input, messageCommandId); | ||
| messageWasAlreadyAccepted = Option.isSome(messageReceipt); | ||
| const messageId = | ||
| input.initialMessage.messageId ?? | ||
| (yield* ids.allocate | ||
|
|
@@ -861,16 +880,24 @@ const make = Effect.gen(function* () { | |
| // A retried root launch prepares the folder its first attempt bound, so | ||
| // a Scratch thread keeps its own. Other root launches bind no folder. | ||
| const boundWorktreePath = projection.thread.worktreePath; | ||
| // A replay prepares what its accepted run recorded. A run this attempt just | ||
| // created only echoes the strategy above, so reading it back would shadow | ||
| // the bound-folder recovery a partial launch still needs. | ||
| const recordedStrategy = | ||
| messageWasAlreadyAccepted && runId !== null | ||
| ? projection.runs.find((run) => run.id === runId)?.workspacePreparation | ||
| : undefined; | ||
| const preparationStrategy: ThreadLaunchWorkspaceStrategy = | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High A replayed Scratch launch can run setup and the agent in the shared project root, losing its per-thread workspace isolation. When the accepted run records 🤖 Copy this AI Prompt to have your agent fix this: |
||
| Option.isSome(launchReceipt) && | ||
| recordedStrategy ?? | ||
| (Option.isSome(launchReceipt) && | ||
| workspaceStrategy.type === "root" && | ||
| boundWorktreePath !== null | ||
| ? { | ||
| type: "existing_worktree", | ||
| worktreePath: boundWorktreePath, | ||
| branch: workspaceStrategy.branch, | ||
| } | ||
| : workspaceStrategy; | ||
| : workspaceStrategy); | ||
| if (shouldSchedule) { | ||
| const ownsPreparation = yield* reservePreparation(input.commandId); | ||
| if (ownsPreparation) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.