mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-22 00:30:19 +02:00
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:
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 () => {
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user