fix(sandbox): resume leases with their effective provider configuration

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-09 23:14:53 -05:00
parent 3f7cbb4f97
commit d619dd85b8
3 changed files with 73 additions and 2 deletions

View File

@ -7,6 +7,12 @@ publication. Repeated error cleanup keeps the original failure visible. A later
authorized run recovers unsaved edits from the retained sandbox before loading
incoming shared files; it does not rewrite the failed run as successful.
Reusable sandbox resume resolves provider configuration from the recorded lease,
as workspace operations and cleanup do. This preserves provider-selected defaults
such as Daytona's region when the environment leaves them unspecified. Existing
configuration and identity checks still reject incompatible reuse; stopping and
resuming a compatible lease must reopen the same account-scoped provider handle.
The deployed acceptance entry point is `pnpm test:e2e:work-folders:deployed`.
Set `PAPERCLIP_DEPLOYED_STACK_MANIFEST` to a JSON manifest matching
`tests/runner-e2e/deployed-stack.ts`, `PAPERCLIP_DEPLOYED_STACK_AUTH` to a private

View File

@ -668,6 +668,61 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
expect(retry.lease.metadata?.sandboxLeaseAcquisition).toEqual({ outcome: "resumed" });
});
it.each(["codex_local", "paperclip_runner"])("resumes %s in the provider region recorded by the lease", async (adapterType) => {
const seeded = await seedReusablePluginSandboxLease(adapterType);
// The environment leaves region selection to the provider. The provider
// records its resolved region, which execute/realize/release already use.
expect(seeded.environment.config).not.toHaveProperty("target");
await environmentService(db).updateLeaseMetadata(seeded.reusableLease.id, {
...seeded.reusableLease.metadata,
target: "us",
});
const closedRegions = new Set<string | undefined>();
const workerManager = {
isRunning: vi.fn(() => true),
getWorker: vi.fn(() => ({ supportedMethods: ["environmentResumeLease", "environmentReleaseLease", "environmentDestroyLease"] })),
call: vi.fn(async (_pluginId: string, method: string, params: { config: { target?: string } }) => {
if (method === "environmentReleaseLease") {
closedRegions.add(params.config.target);
return;
}
if (method === "environmentResumeLease") {
closedRegions.delete(params.config.target);
return { providerLeaseId: seeded.reusableLease.providerLeaseId, metadata: {
provider: "fake-plugin", image: "fake:test", timeoutMs: 1234, reuseLease: true, target: "us",
} };
}
if (method === "environmentRealizeWorkspace") {
if (closedRegions.has(params.config.target)) throw new Error("Sandbox lease is no longer active");
return { cwd: "/workspace" };
}
throw new Error(`Unexpected provider operation: ${method}`);
}),
} as unknown as PluginWorkerManager;
const runtime = environmentRuntimeService(db, { pluginWorkerManager: workerManager });
await runtime.acquireRunLease({
companyId: seeded.companyId, environment: seeded.environment, agentId: seeded.agentId,
heartbeatRunId: seeded.runId, issueId: null, adapterType,
persistedExecutionWorkspace: { id: seeded.executionWorkspaceId, mode: "shared_workspace" },
});
await runtime.releaseRunLeases(seeded.runId, "released", undefined, "stop_and_retain");
expect(closedRegions).toEqual(new Set(["us"]));
const nextRunId = randomUUID();
await db.insert(heartbeatRuns).values({ id: nextRunId, companyId: seeded.companyId, agentId: seeded.agentId, status: "running" });
const acquired = await runtime.acquireRunLease({
companyId: seeded.companyId, environment: seeded.environment, agentId: seeded.agentId,
heartbeatRunId: nextRunId, issueId: null, adapterType,
persistedExecutionWorkspace: { id: seeded.executionWorkspaceId, mode: "shared_workspace" },
});
expect(acquired.lease.providerLeaseId).toBe(seeded.reusableLease.providerLeaseId);
await expect(runtime.realizeWorkspace({
environment: seeded.environment, lease: acquired.lease,
workspace: { localPath: "/workspace", mode: "shared_workspace" },
})).resolves.toMatchObject({ cwd: "/workspace" });
expect(workerManager.call).toHaveBeenCalledWith(seeded.pluginId, "environmentResumeLease",
expect.objectContaining({ config: expect.objectContaining({ target: "us" }) }), expect.any(Number));
});
it("keeps an existing task's legacy sync contract after its sandbox expires, without affecting new tasks", async () => {
const seeded = await seedReusablePluginSandboxLease("codex_local");
const taskId = randomUUID();

View File

@ -2033,9 +2033,19 @@ function createSandboxEnvironmentDriver(
reusableLease = await claimReusableLeaseBeforeResume(reusableLease, input);
if (!reusableLease.providerLeaseId) throw new Error("Reusable sandbox claim lost its provider identity");
try {
// Use the same effective provider configuration as execute and
// release. Providers can resolve an omitted region at creation;
// resuming with the original default would open a different
// account-scoped handle from the one that cleanup closed.
const resumeConfig = await resolvePluginSandboxRuntimeConfig({
environment: input.environment,
lease: reusableLease,
provider: parsed.config.provider,
});
const resumeWorkerConfig = stripSandboxProviderEnvelope(resumeConfig as SandboxEnvironmentConfig);
const resumeDeadline = Date.now() + 60_000;
const configuredResumeTimeoutMs =
resolvePluginSandboxRpcTimeoutMs(workerConfig) ?? 60_000;
resolvePluginSandboxRpcTimeoutMs(resumeWorkerConfig) ?? 60_000;
let retryDelayMs = 250;
let resumed: PluginEnvironmentLease;
while (true) {
@ -2048,7 +2058,7 @@ function createSandboxEnvironmentDriver(
companyId: input.companyId,
environmentId: input.environment.id,
issueId: input.issueId,
config: workerConfig,
config: resumeWorkerConfig,
providerLeaseId: reusableLease.providerLeaseId,
leaseMetadata: reusableLease.metadata ?? undefined,
},