From 3178ef875a453152cd640655ba3c3e0702403862 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 7 Nov 2026 11:02:11 -0600 Subject: [PATCH] fix(native): let a run blocked for a rejected model be retried The status arbiter already blocks the task for a model choice. The release path then opened a native reconciliation incident because the task was blocked, which put the run in recovery_needed and hid its Retry. A rejected model or incomplete configuration has no execution to reconcile; release without the incident. Co-Authored-By: Claude Opus 5.5 --- .../src/modules/wake-queue/adapters/postgres.test.ts | 12 ++++++++++++ server/src/modules/wake-queue/adapters/postgres.ts | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/server/src/modules/wake-queue/adapters/postgres.test.ts b/server/src/modules/wake-queue/adapters/postgres.test.ts index 48649b9f7ce..831d8228d3d 100644 --- a/server/src/modules/wake-queue/adapters/postgres.test.ts +++ b/server/src/modules/wake-queue/adapters/postgres.test.ts @@ -780,6 +780,18 @@ describeEmbeddedPostgres("wake-queue postgres adapter", () => { expect((await db.select().from(issues).where(eq(issues.id, issueId)))[0].statusVersion).toBe(blockedIssue.statusVersion); }); + it("does not open a reconciliation incident for a native run whose model the provider rejected", async () => { + const companyId = await seedCompany(); + const agentId = await seedAgent({ companyId }); + const issueId = await seedIssue({ companyId, assigneeAgentId: agentId, status: "blocked" }); + const runId = await seedRun({ companyId, agentId, status: "failed", contextSnapshot: { issueId } }); + await db.update(heartbeatRuns).set({ runtimeMode: "native", errorCode: "native_provider_model_rejected" }).where(eq(heartbeatRuns.id, runId)); + const adapter = createPostgresWakeQueueAdapter(db, stubDeps); + await adapter.withIssueExecutionLock({ companyId, runId, now: new Date() }, async () => { throw new Error("must not restart a rejected model"); }); + expect((await db.select().from(issues).where(eq(issues.id, issueId)))[0].status).toBe("blocked"); + expect(await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, issueId))).toHaveLength(0); + }); + it.each(["active", "escalated"])("repairs a failed native task with an existing %s recovery action", async (status) => { const companyId = await seedCompany(); const agentId = await seedAgent({ companyId }); diff --git a/server/src/modules/wake-queue/adapters/postgres.ts b/server/src/modules/wake-queue/adapters/postgres.ts index f8c748820fd..100e56b3143 100644 --- a/server/src/modules/wake-queue/adapters/postgres.ts +++ b/server/src/modules/wake-queue/adapters/postgres.ts @@ -1369,6 +1369,13 @@ export function createPostgresWakeQueueAdapter(db: Db, deps: WakeQueuePostgresAd }; } + // A native run blocked for a rejected model or incomplete configuration waits for that + // change and a retry. The provider refused the request, so there is no execution to + // reconcile; a reconciliation incident would also hide the run's Retry. + if (!handoffAssigneeAgentId && run.runtimeMode === "native" && isConfigurationIncompleteFailedRun(run) + && issueRow.status === "blocked" && issueRow.assigneeAgentId === run.agentId) { + return { outcome: { kind: "released" }, postCommitEffects: [], run: runSnapshot }; + } if (!handoffAssigneeAgentId && await recordNativeTerminalRecoveryIfNeeded(tx, run, issueRow, input.now)) { return { outcome: { kind: "released" }, postCommitEffects: [], run: runSnapshot }; }