mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
fix: cloud draft remains stuck after worker teardown (#107759)
* fix: clear destroyed failed cloud drafts * chore: leave cloud fix notes to release flow
This commit is contained in:
committed by
GitHub
parent
fd986ed3c2
commit
31cd030f16
@@ -50,10 +50,18 @@ export function resolveSessionWorkerPlacementMutationError(params: {
|
||||
const placement = params.context.workerSessionPlacementService
|
||||
?.getMany([params.sessionId])
|
||||
.get(params.sessionId);
|
||||
// Failed placement normally keeps destructive mutation fenced. Missing worker identity or an
|
||||
// authoritative destroyed environment proves cleanup cannot orphan a live worker.
|
||||
const failedPlacementCanDelete =
|
||||
params.action === "delete" &&
|
||||
placement?.state === "failed" &&
|
||||
(placement.environmentId === null ||
|
||||
params.context.workerEnvironmentService?.get(placement.environmentId)?.state === "destroyed");
|
||||
if (
|
||||
!placement ||
|
||||
placement.state === "local" ||
|
||||
(params.action === "delete" && placement.state === "reclaimed")
|
||||
(params.action === "delete" && placement.state === "reclaimed") ||
|
||||
failedPlacementCanDelete
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,13 @@ test("sessions.delete rejects failed placement with unresolved worker ownership"
|
||||
"sessions.delete",
|
||||
{ key: sessionKey },
|
||||
{
|
||||
context: { workerSessionPlacementService: placementReader },
|
||||
context: {
|
||||
workerEnvironmentService: {
|
||||
get: () => ({ state: "attached" }),
|
||||
resolveInferenceSessionForRunId: () => undefined,
|
||||
} as never,
|
||||
workerSessionPlacementService: placementReader,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -182,6 +188,60 @@ test("sessions.delete rejects failed placement with unresolved worker ownership"
|
||||
expect(embeddedRunMock.abortCalls).toEqual([]);
|
||||
});
|
||||
|
||||
test("sessions.delete allows failed placement after its worker is destroyed", async () => {
|
||||
await createSessionStoreDir();
|
||||
const sessionKey = "discord:group:destroyed-failed-worker-session";
|
||||
const sessionId = "sess-destroyed-failed-worker-delete";
|
||||
await writeSessionStore({ entries: { [sessionKey]: sessionStoreEntry(sessionId) } });
|
||||
const placementReader = sequencedPlacementReader([terminalPlacementRecord(sessionId, "failed")]);
|
||||
|
||||
const deleted = await directSessionReq(
|
||||
"sessions.delete",
|
||||
{ key: sessionKey },
|
||||
{
|
||||
context: {
|
||||
workerEnvironmentService: {
|
||||
get: (environmentId: string) => {
|
||||
expect(environmentId).toBe("worker-environment");
|
||||
return { state: "destroyed" };
|
||||
},
|
||||
hasInferenceForSession: () => false,
|
||||
resolveInferenceSessionForRunId: () => undefined,
|
||||
} as never,
|
||||
workerSessionPlacementService: placementReader,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(deleted.ok).toBe(true);
|
||||
expect(deleted.payload).toMatchObject({ ok: true, deleted: true });
|
||||
expect(loadSessionEntry(sessionKey).entry).toBeUndefined();
|
||||
});
|
||||
|
||||
test("sessions.delete allows failed placement that never acquired a worker", async () => {
|
||||
await createSessionStoreDir();
|
||||
const sessionKey = "discord:group:unallocated-failed-worker-session";
|
||||
const sessionId = "sess-unallocated-failed-worker-delete";
|
||||
await writeSessionStore({ entries: { [sessionKey]: sessionStoreEntry(sessionId) } });
|
||||
const placement = terminalPlacementRecord(sessionId, "failed");
|
||||
if (placement.state !== "failed") {
|
||||
throw new Error("expected failed placement fixture");
|
||||
}
|
||||
placement.environmentId = null;
|
||||
|
||||
const deleted = await directSessionReq(
|
||||
"sessions.delete",
|
||||
{ key: sessionKey },
|
||||
{
|
||||
context: { workerSessionPlacementService: sequencedPlacementReader([placement]) },
|
||||
},
|
||||
);
|
||||
|
||||
expect(deleted.ok).toBe(true);
|
||||
expect(deleted.payload).toMatchObject({ ok: true, deleted: true });
|
||||
expect(loadSessionEntry(sessionKey).entry).toBeUndefined();
|
||||
});
|
||||
|
||||
test("sessions.delete allows reclaimed placement with no live worker owner", async () => {
|
||||
await createSessionStoreDir();
|
||||
const sessionKey = "discord:group:reclaimed-worker-session";
|
||||
|
||||
Reference in New Issue
Block a user