diff --git a/server/src/__tests__/heartbeat-workspace-session.test.ts b/server/src/__tests__/heartbeat-workspace-session.test.ts index a7036a99ed9..215ec451b71 100644 --- a/server/src/__tests__/heartbeat-workspace-session.test.ts +++ b/server/src/__tests__/heartbeat-workspace-session.test.ts @@ -41,6 +41,7 @@ import { stripHostWorkspaceProvisionForLowTrustSandbox, stripWorkspaceRuntimeFromExecutionRunConfig, shouldResetTaskSessionForModelChange, + shouldReusePinnedExecutionWorkspaceForRun, stripConfiguredModelFromSessionParams, stripPaperclipSessionMetadataFromSessionParams, normalizeSessionParams, @@ -1660,6 +1661,33 @@ describe("effective run execution workspace config freshness", () => { }); }); + it("does not reuse an available shared pin for a low-trust isolated run", () => { + expect(shouldReusePinnedExecutionWorkspaceForRun({ + requestedShouldReuseExisting: true, + trustPresetKind: "low_trust_review", + requestedExecutionWorkspaceMode: "isolated_workspace", + existingExecutionWorkspaceMode: "shared_workspace", + })).toBe(false); + }); + + it("reuses an available isolated pin for a low-trust isolated run", () => { + expect(shouldReusePinnedExecutionWorkspaceForRun({ + requestedShouldReuseExisting: true, + trustPresetKind: "low_trust_review", + requestedExecutionWorkspaceMode: "isolated_workspace", + existingExecutionWorkspaceMode: "isolated_workspace", + })).toBe(true); + }); + + it("keeps unavailable low-trust pins on the named reuse-failure path", () => { + expect(shouldReusePinnedExecutionWorkspaceForRun({ + requestedShouldReuseExisting: true, + trustPresetKind: "low_trust_review", + requestedExecutionWorkspaceMode: "isolated_workspace", + existingExecutionWorkspaceMode: null, + })).toBe(true); + }); + it.each([ { name: "missing", status: null }, { name: "archived", status: "archived" }, diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index a14a3389a5a..a09687d034e 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -4355,6 +4355,25 @@ export function resolveExecutionWorkspaceReuseRequestForIssue(input: { }; } +export function shouldReusePinnedExecutionWorkspaceForRun(input: { + requestedShouldReuseExisting: boolean; + trustPresetKind: TrustPresetResolution["kind"]; + requestedExecutionWorkspaceMode: ReturnType; + existingExecutionWorkspaceMode: string | null; +}): boolean { + if (!input.requestedShouldReuseExisting) return false; + + // A durable pin cannot weaken the low-trust isolation boundary. An available + // non-isolated pin is bypassed so the policy can provision a fresh isolated workspace. + // Missing or archived pins still flow through the named reuse-failure path. + return !( + input.trustPresetKind === "low_trust_review" + && input.requestedExecutionWorkspaceMode === "isolated_workspace" + && input.existingExecutionWorkspaceMode !== null + && input.existingExecutionWorkspaceMode !== "isolated_workspace" + ); +} + export function resolveExecutionWorkspaceReuseProvisioningPolicy(input: { requestedShouldReuseExisting: boolean; workspaceConfigFreshness: ExecutionWorkspaceConfigFreshnessDecision; @@ -14020,8 +14039,16 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {}) issueExecutionWorkspacePreference: issueRef?.executionWorkspacePreference ?? null, existingExecutionWorkspaceStatus: existingExecutionWorkspace?.status ?? null, }); - const requestedShouldReuseExisting = workspaceReuseRequest.requestedShouldReuseExisting; - const reusableExistingExecutionWorkspace = workspaceReuseRequest.existingExecutionWorkspaceAvailable + const requestedShouldReuseExisting = shouldReusePinnedExecutionWorkspaceForRun({ + requestedShouldReuseExisting: workspaceReuseRequest.requestedShouldReuseExisting, + trustPresetKind: trustPreset.kind, + requestedExecutionWorkspaceMode, + existingExecutionWorkspaceMode: workspaceReuseRequest.existingExecutionWorkspaceAvailable + ? existingExecutionWorkspace?.mode ?? null + : null, + }); + const reusableExistingExecutionWorkspace = requestedShouldReuseExisting + && workspaceReuseRequest.existingExecutionWorkspaceAvailable ? existingExecutionWorkspace : null; if (