Clean failed reusable leases when issues and workspaces close

Include failed reusable leases in existing company-scoped closure cleanup. Preserve live-run protection. Validate both closure scopes and the company boundary with 282 environment tests and the server typecheck. Record the remaining build, qualification, browser CI, and review gates.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-06 16:36:56 -05:00
1 parent 3f6b11a1e5
commit fc661a3647
3 files changed
+54 -6

No files matched your search

@@ -40,6 +40,39 @@ The memory, steering and Runner terminal/tool-call failures still need their
own proven corrections; this cleanup change does not authorize their retries.
All 26 Product and seven Runner cases remain required on the final set.
Review also identifies the issue/workspace closure path, which omitted failed
reusable leases. The follow-up includes them there while retaining company and
issue/workspace scope and the live-run check. All 282 environment tests pass,
including failed-lease closure for each scope and rejection of another company.
The complete server typecheck passes with all corrected services and tests.
The corrected-suite log hash is
`9b965b9cf0acc1dc49340f85d4efb5588f6f59b3bc2522df7ec96d3bb515443a`.
The first filtered review regression reaches its unchanged database startup
hook timeout before any test executes. That failure stays preserved and its
owned Postgres process is independently confirmed absent. It proves no semantic
regression result.
Two unchanged pinned-runtime controls also pass with no provider credentials:
native agent-file write/read preserves the exact final LF, and serialized RPC
warm recovery preserves that memory across both sessions and reaches both
terminal turns at low thinking. The synthetic server supplies six requests;
external socket connections are denied before connect. The log hash is
`b902ea76b2d0ee377dc28c42e7fe96d91e9e92671060380fe2dd87901b801797`.
These controls narrow the failing live memory/terminal investigation. They do
not attest its missing raw write arguments or regrade either live failure.
The first cleanup revision `3f6b11a1e` passes full build, recursive typecheck and
Product harness typecheck. Its qualification-only Linux image is built and
published at
`ghcr.io/paperclipai/paperclip-daytona-runner@sha256:ab05e949af8ccff1a1f94317272ce5d934398a9548b6197de273bfb942bf74a4`.
The issue/workspace follow-up changes shipping inputs again, so that intermediate
image is historical and is not admitted or qualified. Preserve its receipts.
No new paid attempts run in either correction. Latest-head CI and review remain
required. The first cleanup head fails its connection-intent browser scenario:
its continuation reaches `cancelled` where the unchanged test requires
`succeeded`. Keep that failure and inspect the actual run before changing code.
## Linux process-birth correction — 2026-10-06
The prior-set local pending-question restart failed before controller shutdown because the
@@ -6662,13 +6662,24 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
});
});
it("destroys scoped reusable plugin-backed sandbox leases", async () => {
it.each([
{ status: "active", scope: "workspace" },
{ status: "failed", scope: "workspace" },
{ status: "failed", scope: "issue" },
] as const)("destroys $status reusable plugin-backed sandbox leases scoped to an $scope", async ({ status, scope }) => {
const { pluginId, companyId, runId, executionWorkspaceId, reusableLease } =
await seedReusablePluginSandboxLease();
await db
.update(heartbeatRuns)
.set({ status: "succeeded" })
.set({ status: status === "failed" ? "failed" : "succeeded" })
.where(eq(heartbeatRuns.id, runId));
if (status === "failed") await environmentService(db).releaseLease(reusableLease.id, "failed");
const issueId = randomUUID();
if (scope === "issue") {
await db.insert(issues).values({ id: issueId, companyId, title: "Failed reusable lease cleanup", status: "done", priority: "medium" });
await db.update(environmentLeases).set({ issueId }).where(eq(environmentLeases.id, reusableLease.id));
}
const selection = scope === "issue" ? { issueId } : { executionWorkspaceId };
const workerManager = {
isRunning: vi.fn((id: string) => id === pluginId),
@@ -6682,9 +6693,12 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
} as unknown as PluginWorkerManager;
const runtimeWithPlugin = environmentRuntimeService(db, { pluginWorkerManager: workerManager });
// A known issue/workspace id does not authorize another company's teardown.
expect(await runtimeWithPlugin.destroyReusableSandboxLeases({ companyId: randomUUID(), ...selection })).toEqual([]);
expect(workerManager.call).not.toHaveBeenCalled();
const destroyed = await runtimeWithPlugin.destroyReusableSandboxLeases({
companyId,
executionWorkspaceId,
...selection,
failureReason: "execution_workspace_closed",
});
@@ -6707,9 +6721,10 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
});
});
it("does not destroy a scoped reusable lease while its run is active", async () => {
it.each(["active", "failed"] as const)("does not destroy a %s scoped reusable lease while its run is active", async (status) => {
const { pluginId, companyId, executionWorkspaceId, reusableLease } =
await seedReusablePluginSandboxLease();
if (status === "failed") await environmentService(db).releaseLease(reusableLease.id, "failed");
const workerManager = {
isRunning: vi.fn((id: string) => id === pluginId),
call: vi.fn(async () => undefined),
@@ -6735,7 +6750,7 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
expect(workerManager.call).not.toHaveBeenCalled();
await expect(
environmentService(db).getLeaseById(reusableLease.id),
).resolves.toMatchObject({ status: "active" });
).resolves.toMatchObject({ status });
});
it("destroys reusable plugin-backed sandbox leases scoped to an environment", async () => {
+1 -1
View File
@@ -4161,7 +4161,7 @@ export function environmentRuntimeService(
and(
eq(environmentLeases.companyId, input.companyId),
eq(environmentLeases.leasePolicy, "reuse_by_environment"),
inArray(environmentLeases.status, ["active", "released", "retained", "pending_cleanup"]),
inArray(environmentLeases.status, ["active", "released", "retained", "failed", "pending_cleanup"]),
...scopeConditions,
),
);