From 9be5ad06c1697a4f55e381add4193b36ca2cbf2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 8 Oct 2026 07:42:16 -0400 Subject: [PATCH] Q42: claimed runs that fail in preparation close cleanly and are logged Co-Authored-By: Claude Opus 5.5 --- src/main/app/task-queue-worker.ts | 5 +++-- tests/integration/run-memory.test.ts | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/app/task-queue-worker.ts b/src/main/app/task-queue-worker.ts index 895a0859..0b23e03d 100644 --- a/src/main/app/task-queue-worker.ts +++ b/src/main/app/task-queue-worker.ts @@ -112,8 +112,9 @@ export function startTaskQueueWorker(input: TaskQueueWorkerInput): () => void { inFlight[slot] = true; notifyDataChanged(); void taskExecution.executeRun(claimed.org_id, claimed.id) - .catch(() => { - // executeRun records the failure on the run row; the UI reflects it. + .catch((error: unknown) => { + // executeRun already marked the run failed; log so the cause is visible outside the UI. + console.error(`[task-queue] run ${claimed.id} failed: ${error instanceof Error ? error.message : String(error)}`); }) .finally(() => { inFlight[slot] = false; diff --git a/tests/integration/run-memory.test.ts b/tests/integration/run-memory.test.ts index aab6c4aa..e2c65925 100644 --- a/tests/integration/run-memory.test.ts +++ b/tests/integration/run-memory.test.ts @@ -87,6 +87,19 @@ describe("run memory", () => { h.connection.close(); }); + test("a claimed run whose agent lost its provider fails cleanly and frees the task", async () => { + const h = await company(() => ({ text: "unused", toolCalls: [], rawResponseJson: "{}" })); + const kickoff = claimNextQueuedRun(h.connection)!; + h.connection.db.prepare("update agents set model_provider_id = null where id = ?").run(h.ceoId); + await expect(h.execution.executeRun(h.orgId, kickoff.id)).rejects.toThrow("Task assignee requires a model provider."); + expect(h.connection.db.prepare("select state, error_message, completed_at is not null as closed from task_runs where id = ?").get(kickoff.id)) + .toEqual({ state: "failed", error_message: "Task assignee requires a model provider.", closed: 1 }); + expect(h.connection.db.prepare("select count(*) as n from task_runs where state in ('queued', 'running')").get()).toEqual({ n: 0 }); + expect(h.connection.db.prepare("select action, json_extract(payload_json, '$.stage') as stage from audit_events where entity_id = ? and action = 'failed'").get(kickoff.id)) + .toEqual({ action: "failed", stage: "preparation" }); + h.connection.close(); + }); + test("chat replies are not auto-recorded", async () => { const h = await company((prompt) => ({ text: /thread_id="/.test(prompt) ? "Hiring two engineers this week." : "Kickoff noted.", toolCalls: [], rawResponseJson: "{}" })); const kickoff = claimNextQueuedRun(h.connection)!;