From 2cca8a95388d809f52ac65a5ed4ada93bbe5d2d9 Mon Sep 17 00:00:00 2001 From: xiangyingchang <921162442@qq.com> Date: Sat, 14 Nov 2026 18:07:43 +0800 Subject: [PATCH 1/2] fix(server): recover Codex threads after encrypted history rejection --- .../Adapters/CodexAdapterV2.test.ts | 18 ++ .../Adapters/CodexAdapterV2.ts | 5 +- .../ProviderTurnStartService.ts | 83 +++++- .../ProviderSwitch.integration.test.ts | 275 +++++++++++++++++- .../server/src/provider/CodexManagedErrors.ts | 4 + 5 files changed, 369 insertions(+), 16 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index 884e953a495d6..868389bf30a00 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -6953,6 +6953,23 @@ describe("CodexAdapterV2 post-settle continuation", () => { expectedCode: "subscription_sharing_usage_limit_exceeded", expectedMessage: "Usage settings", }, + { + name: "invalid-encrypted-history", + code: "other", + message: JSON.stringify({ + error: { + type: "invalid_request_error", + code: "invalid_encrypted_content", + message: "Encrypted content could not be decrypted or parsed.", + }, + headers: { "x-codex-turn-state": "dummy-sensitive" }, + status: 400, + }), + notification: true, + expectedClass: "provider_error", + expectedCode: "invalid_encrypted_content", + expectedMessage: "encrypted conversation history", + }, { name: "sharing-code-mention-retry", code: "responseStreamDisconnected", @@ -7224,6 +7241,7 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.equal(terminal?.status, "failed"); if (terminal?.status !== "failed") return; assert.equal(terminal.failure.class, scenario.expectedClass); + assert.notInclude(terminal.failure.message, "dummy-sensitive"); assert.equal(terminal.threadDisposition, "reusable"); if (scenario.name === "deferred-reset" || scenario.name === "first-request-reset") assert.equal(terminal.failure.resetAt, resetAt); diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index 7de690c5e47d0..ee5ff41ddb3ae 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -1194,9 +1194,8 @@ function codexUsageLimitError( } /** - * ChatGPT sharing reports its own reason inside Codex's generic turn error, so - * the code it names replaces Codex's, and clients pick a recovery from it. Its - * allowance limits get the usage-limit recovery; other sharing failures keep their class. + * Codex wraps structured service failures in generic turn errors. Preserve the + * inner code and safe message; sharing allowance limits also change the class. */ function withCodexManagedFailure( failure: OrchestrationV2ProviderFailure, diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index 15792dacecfa2..ce49cb309ddd2 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -1,3 +1,4 @@ +import { classifyCodexManagedError } from "../provider/CodexManagedErrors.ts"; import { modelSelectionsEqual } from "@t3tools/shared/model"; import { projectComposerContextForProvider } from "@t3tools/shared/composerContextReferences"; import { @@ -1258,15 +1259,85 @@ export const layer: Layer.Layer< handoff.delivery?.nativeThreadId === providerThread.nativeThreadRef?.nativeId && handoff.delivery?.status === "pending", ); + const rejectedHistory = yield* Effect.gen(function* () { + if (session.driver !== "codex") return false; + const acceptedAttemptIds = new Set( + projection.providerTurns.map((turn) => turn.runAttemptId), + ); + const previous = projection.runs.findLast( + (source) => + source.providerThreadId === providerThread.id && + source.ordinal < run.ordinal && + source.activeAttemptId !== null && + acceptedAttemptIds.has(source.activeAttemptId), + ); + if ( + previous === undefined || + (previous.status !== "failed" && + previous.status !== "interrupted" && + previous.status !== "cancelled") + ) + return false; + const previousAttempt = projection.attempts.find( + (source) => source.id === previous.activeAttemptId, + ); + if (previousAttempt === undefined) return false; + if (previousAttempt.nativeThreadId !== undefined) { + if (previousAttempt.nativeThreadId !== providerThread.nativeThreadRef?.nativeId) + return false; + } else if ( + projection.contextHandoffs.some( + (handoff) => + handoff.toProviderThreadId === providerThread.id && + handoff.strategy === "full_thread_summary" && + handoff.delivery?.nativeThreadId === providerThread.nativeThreadRef?.nativeId && + handoff.coveredRunOrdinals.to >= previous.ordinal, + ) + ) + return false; + // The native resume RPC can succeed while persisted input still rejects every turn. + return (yield* projectionStore.getTurnStartHistory(input.threadId, [previous.id])).some( + (item) => + item.type === "error" && + item.nodeId === previous.rootNodeId && + (item.failure.code === "invalid_encrypted_content" || + classifyCodexManagedError(item.failure.message)?.code === + "invalid_encrypted_content"), + ); + }); + if ( + rejectedHistory && + (yield* ( + session.hasPendingBackgroundWorkForThread?.(providerThread, { + includeSubagents: true, + }) ?? + session.hasPendingBackgroundWork ?? + Effect.succeed(false) + )) + ) { + return yield* loadFromProvider( + Effect.fail( + new ProviderAdapter.ProviderAdapterTurnStartError({ + driver: session.driver, + threadId: projection.thread.id, + providerThreadId: providerThread.id, + runId, + cause: new ContextHandoffBackgroundWorkError(), + }), + ), + ); + } const resumed = yield* Effect.result( - uncertainDelivery + uncertainDelivery || rejectedHistory ? Effect.fail( new ProviderAdapter.ProviderAdapterTurnStartError({ driver: session.driver, threadId: projection.thread.id, providerThreadId: providerThread.id, runId, - cause: "Uncertain native history injection", + cause: rejectedHistory + ? "Rejected native history" + : "Uncertain native history injection", }), ) : session.resumeThread({ @@ -1346,9 +1417,11 @@ export const layer: Layer.Layer< runId, reason: uncertainDelivery ? "uncertain_history_delivery" - : resumed._tag === "Success" - ? "handoff_budget_exhausted" - : "resume_failed", + : rejectedHistory + ? "rejected_native_history" + : resumed._tag === "Success" + ? "handoff_budget_exhausted" + : "resume_failed", errorTag: resumed._tag === "Failure" ? resumed.failure._tag : "ContextHandoffBudgetError", }); const replacement = yield* loadFromProvider( diff --git a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts index 965e5bd44f2dc..53ff2596d51e9 100644 --- a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts +++ b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts @@ -115,6 +115,7 @@ function makeTestAdapter(input: { Record> >; readonly failure?: ReturnType; + readonly rejectedNativeThreads?: Ref.Ref>; readonly failedRunOrdinals?: ReadonlySet; readonly interruptedRunOrdinals?: ReadonlySet; readonly cancelledRunOrdinals?: ReadonlySet; @@ -345,13 +346,27 @@ function makeTestAdapter(input: { yield* Deferred.await(input.releaseFirstTurn); } const eventTime = yield* DateTime.now; - const terminalStatus = input.failedRunOrdinals?.has(turnInput.runOrdinal) - ? "failed" - : input.interruptedRunOrdinals?.has(turnInput.runOrdinal) - ? "interrupted" - : input.cancelledRunOrdinals?.has(turnInput.runOrdinal) - ? "cancelled" - : "completed"; + const nativeThreadId = turnInput.providerThread.nativeThreadRef?.nativeId ?? ""; + if ( + input.rejectedNativeThreads && + input.failedRunOrdinals?.has(turnInput.runOrdinal) + ) { + yield* Ref.update( + input.rejectedNativeThreads, + (ids) => new Set([...ids, nativeThreadId]), + ); + } + const rejectedHistory = + input.rejectedNativeThreads !== undefined && + (yield* Ref.get(input.rejectedNativeThreads)).has(nativeThreadId); + const terminalStatus = + input.failedRunOrdinals?.has(turnInput.runOrdinal) || rejectedHistory + ? "failed" + : input.interruptedRunOrdinals?.has(turnInput.runOrdinal) + ? "interrupted" + : input.cancelledRunOrdinals?.has(turnInput.runOrdinal) + ? "cancelled" + : "completed"; const response = input.responseByThreadId?.[turnInput.threadId]?.[turnInput.runOrdinal] ?? input.responseByRunOrdinal[turnInput.runOrdinal] ?? @@ -400,7 +415,7 @@ function makeTestAdapter(input: { messageId: MessageId.make( `message:${input.driver}:${turnInput.threadId}:${turnInput.runOrdinal}:assistant`, ), - text: response, + text: rejectedHistory ? "" : response, streaming: false, }, }, @@ -1369,6 +1384,250 @@ describe("orchestration v2 provider switching", () => { }), ); + it.live.each([ + "live", + "detached", + "legacy", + "interrupted", + "cancelled", + "other-error", + "other-provider", + "repaired-native", + "raw-error", + "incidental-mention", + "background", + ] as const)("recovers invalid encrypted Codex history on the next prompt: %s", (scenario) => + Effect.scoped( + Effect.gen(function* () { + const cwd = yield* checkpointWorkspace("codex-rejected-history"); + const capturedTurns = yield* Ref.make>([]); + const injectedHistory = yield* Ref.make>([]); + const generation = yield* Ref.make(0); + const rejectedNativeThreads = yield* Ref.make>(new Set()); + const pendingBackgroundWork = yield* Ref.make(false); + const modelSelection = + scenario === "other-provider" ? CLAUDE_MODEL_SELECTION : CODEX_MODEL_SELECTION; + const code = + scenario === "raw-error" || scenario === "incidental-mention" + ? "other" + : scenario === "other-error" + ? "serverOverloaded" + : "invalid_encrypted_content"; + const registry = ProviderAdapterRegistry.layerFromAdapters([ + makeTestAdapter({ + instanceId: modelSelection.instanceId, + driver: scenario === "other-provider" ? CLAUDE_DRIVER : CODEX_DRIVER, + capabilities: + scenario === "other-provider" + ? ClaudeProviderCapabilitiesV2 + : CodexProviderCapabilitiesV2, + modelSelection, + capturedTurns, + injectedHistory, + nativeThreadGeneration: generation, + rejectedNativeThreads, + pendingBackgroundWork, + subagentOnlyBackgroundWork: true, + failedRunOrdinals: new Set([2]), + failure: makeProviderFailure({ + code, + message: + scenario === "raw-error" + ? JSON.stringify({ + error: { + code: "invalid_encrypted_content", + message: "Encrypted content could not be verified", + }, + }) + : scenario === "incidental-mention" + ? "Transport failed; this is not invalid_encrypted_content." + : "Encrypted content could not be verified", + class: "provider_error", + }), + responseByRunOrdinal: { 1: "BASELINE_OK", 3: "RECOVERY_OK", 4: "FOLLOWUP_OK" }, + }), + ]); + yield* Effect.gen(function* () { + const orchestrator = yield* Orchestrator.OrchestratorV2; + const worker = yield* EffectWorker.OrchestrationEffectWorkerV2; + const dispatch = Effect.fn("rejectedHistory.dispatch")(function* ( + ordinal: number, + text: string, + ) { + yield* orchestrator.dispatch({ + type: "message.dispatch", + commandId: CommandId.make(`rejected:${ordinal}`), + threadId, + messageId: MessageId.make(`rejected:${ordinal}`), + createdBy: "user", + creationSource: "web", + text, + attachments: [], + modelSelection, + dispatchMode: { type: "start_immediately" }, + }); + yield* orchestrator.streamStoredEvents.pipe( + Stream.filter( + ({ event }) => + event.type === "run.updated" && + event.payload.ordinal === ordinal && + (event.payload.status === "completed" || event.payload.status === "failed"), + ), + Stream.runHead, + ); + yield* worker.drain(); + return yield* orchestrator.getThreadProjection(threadId); + }); + yield* orchestrator.dispatch({ + type: "thread.create", + commandId: CommandId.make("rejected:create"), + threadId, + projectId, + createdBy: "user", + creationSource: "web", + title: "Rejected Codex history", + modelSelection, + runtimeMode: "full-access", + interactionMode: "default", + branch: null, + worktreePath: null, + }); + assert.equal( + (yield* dispatch(1, "Reply BASELINE_OK; remember the constraint")).runs.at(-1)?.status, + "completed", + ); + const failed = yield* dispatch(2, "Continue using encrypted history"); + assert.equal(failed.runs.at(-1)?.status, "failed"); + assert.isTrue( + failed.turnItems.some((item) => item.type === "error" && item.failure.code === code), + ); + if (scenario === "detached" || scenario === "legacy") { + yield* orchestrator.dispatch({ + type: "provider-session.detach", + commandId: CommandId.make("rejected:detach"), + threadId, + providerSessionId: failed.providerThreads[0]!.providerSessionId!, + }); + yield* worker.drain(); + } + if (scenario === "legacy") { + yield* (yield* EventSink.EventSinkV2).write({ + events: failed.attempts.map( + ({ nativeThreadId: _nativeThreadId, ...attempt }, index) => ({ + id: EventId.make(`rejected:legacy:${index}`), + type: "run-attempt.updated" as const, + threadId, + occurredAt: failed.thread.createdAt, + payload: attempt, + }), + ), + }); + } + if (scenario === "interrupted" || scenario === "cancelled") { + yield* (yield* EventSink.EventSinkV2).write({ + events: [ + { + id: EventId.make(`rejected:${scenario}`), + type: "run.updated", + threadId, + occurredAt: failed.thread.createdAt, + payload: { ...failed.runs.at(-1)!, status: scenario }, + }, + ], + }); + } + if (scenario === "repaired-native") { + const providerThread = failed.providerThreads[0]!; + yield* (yield* EventSink.EventSinkV2).write({ + events: [ + { + id: EventId.make("rejected:repaired-native"), + type: "provider-thread.updated", + threadId, + occurredAt: failed.thread.createdAt, + payload: { + ...providerThread, + nativeThreadRef: { + ...providerThread.nativeThreadRef!, + nativeId: "already-repaired", + }, + }, + }, + ], + }); + } + if (scenario === "background") yield* Ref.set(pendingBackgroundWork, true); + const recovered = yield* dispatch(3, "huh"); + if (scenario === "repaired-native") { + assert.equal(recovered.runs.at(-1)?.status, "completed"); + assert.equal(yield* Ref.get(generation), 1); + assert.lengthOf(recovered.contextHandoffs, 0); + return; + } + if ( + scenario === "other-error" || + scenario === "incidental-mention" || + scenario === "other-provider" || + scenario === "background" + ) { + assert.equal(recovered.runs.at(-1)?.status, "failed"); + assert.equal(yield* Ref.get(generation), 1); + assert.lengthOf(yield* Ref.get(capturedTurns), scenario === "background" ? 2 : 3); + if (scenario === "background") { + yield* Ref.set(pendingBackgroundWork, false); + assert.equal( + (yield* dispatch(4, "Continue after the worker finished")).runs.at(-1)?.status, + "completed", + ); + assert.equal(yield* Ref.get(generation), 2); + } + return; + } + assert.equal(recovered.runs.at(-1)?.status, "completed"); + assert.equal(yield* Ref.get(generation), 2); + const turns = yield* Ref.get(capturedTurns); + assert.lengthOf(turns, 3); + assert.equal(turns[2]!.text, "huh"); + assert.notEqual(turns[2]!.nativeThreadId, turns[1]!.nativeThreadId); + assert.equal(turns[2]!.providerThreadId, turns[1]!.providerThreadId); + const history = yield* encodeJson(yield* Ref.get(injectedHistory)); + assert.include(history, "remember the constraint"); + assert.include(history, "BASELINE_OK"); + assert.notInclude(history, '"encrypted_content"'); + assert.notInclude(history, "huh"); + assert.isTrue( + recovered.turnItems.some( + (item) => item.type === "assistant_message" && item.text === "RECOVERY_OK", + ), + ); + const next = yield* dispatch(4, "ok"); + assert.equal(next.runs.at(-1)?.status, "completed"); + assert.equal(yield* Ref.get(generation), 2); + assert.equal( + (yield* Ref.get(capturedTurns))[3]!.nativeThreadId, + turns[2]!.nativeThreadId, + ); + assert.equal(yield* encodeJson(yield* Ref.get(injectedHistory)), history); + assert.equal(next.contextHandoffs.length, recovered.contextHandoffs.length); + }).pipe( + Effect.provide( + ProviderReplayHarness.layerWithRegistry( + { + name: "codex-rejected-history", + runtimePolicyOverride: { + cwd, + approvalPolicy: "never", + sandboxPolicy: { type: "readOnly" }, + }, + }, + registry, + ), + ), + ); + }), + ), + ); + it.live.each(["native", "legacy"] as const)( "starts a replacement native thread as new after the resume fallback with %s attempts", (attempts) => diff --git a/apps/server/src/provider/CodexManagedErrors.ts b/apps/server/src/provider/CodexManagedErrors.ts index 4cad5709d2eb0..a699d31eaae67 100644 --- a/apps/server/src/provider/CodexManagedErrors.ts +++ b/apps/server/src/provider/CodexManagedErrors.ts @@ -34,6 +34,10 @@ const legacyFailures = { }, } as const; const failures = { + invalid_encrypted_content: { + message: + "Codex could not verify this session's encrypted conversation history. Send another message to continue in a fresh session with the visible conversation history.", + }, ...legacyFailures, subscription_sharing_user_not_eligible: legacyFailures.subscription_sharing_v2_user_not_eligible, subscription_sharing_route_not_supported: From b5ed144625b180d2d7aa6a2693b98e421b895450 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 10:29:27 +0000 Subject: [PATCH 2/2] refactor(server): trim encrypted-history recovery tests and share the background-work guard Co-Authored-By: Claude Opus 5.5 --- .../ProviderTurnStartService.ts | 60 ++++++------------- .../ProviderSwitch.integration.test.ts | 52 ++++------------ 2 files changed, 30 insertions(+), 82 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index ce49cb309ddd2..8cb515589d059 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -1305,27 +1305,24 @@ export const layer: Layer.Layer< "invalid_encrypted_content"), ); }); - if ( - rejectedHistory && - (yield* ( - session.hasPendingBackgroundWorkForThread?.(providerThread, { - includeSubagents: true, - }) ?? - session.hasPendingBackgroundWork ?? - Effect.succeed(false) - )) - ) { - return yield* loadFromProvider( - Effect.fail( - new ProviderAdapter.ProviderAdapterTurnStartError({ - driver: session.driver, - threadId: projection.thread.id, - providerThreadId: providerThread.id, - runId, - cause: new ContextHandoffBackgroundWorkError(), - }), - ), - ); + // A fresh session would orphan background work still running on the native thread. + const hasPendingWork = (thread: OrchestrationV2ProviderThread) => + session.hasPendingBackgroundWorkForThread?.(thread, { includeSubagents: true }) ?? + session.hasPendingBackgroundWork ?? + Effect.succeed(false); + const failForPendingWork = loadFromProvider( + Effect.fail( + new ProviderAdapter.ProviderAdapterTurnStartError({ + driver: session.driver, + threadId: projection.thread.id, + providerThreadId: providerThread.id, + runId, + cause: new ContextHandoffBackgroundWorkError(), + }), + ), + ); + if (rejectedHistory && (yield* hasPendingWork(providerThread))) { + return yield* failForPendingWork; } const resumed = yield* Effect.result( uncertainDelivery || rejectedHistory @@ -1378,26 +1375,7 @@ export const layer: Layer.Layer< Effect.catchTags({ ContextHandoffBudgetError: () => Effect.succeed(false) }), ); if (!fitsFresh) return resumed.success; - const hasPendingWork = yield* ( - session.hasPendingBackgroundWorkForThread?.(resumed.success, { - includeSubagents: true, - }) ?? - session.hasPendingBackgroundWork ?? - Effect.succeed(false) - ); - if (hasPendingWork) { - return yield* loadFromProvider( - Effect.fail( - new ProviderAdapter.ProviderAdapterTurnStartError({ - driver: session.driver, - threadId: projection.thread.id, - providerThreadId: providerThread.id, - runId, - cause: new ContextHandoffBackgroundWorkError(), - }), - ), - ); - } + if (yield* hasPendingWork(resumed.success)) return yield* failForPendingWork; } resumedHandoffContext = undefined; resumedRetryHandoff = []; diff --git a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts index 53ff2596d51e9..4f4c7e213c8ee 100644 --- a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts +++ b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts @@ -1386,15 +1386,10 @@ describe("orchestration v2 provider switching", () => { it.live.each([ "live", - "detached", "legacy", - "interrupted", - "cancelled", + "raw-error", "other-error", - "other-provider", "repaired-native", - "raw-error", - "incidental-mention", "background", ] as const)("recovers invalid encrypted Codex history on the next prompt: %s", (scenario) => Effect.scoped( @@ -1405,23 +1400,18 @@ describe("orchestration v2 provider switching", () => { const generation = yield* Ref.make(0); const rejectedNativeThreads = yield* Ref.make>(new Set()); const pendingBackgroundWork = yield* Ref.make(false); - const modelSelection = - scenario === "other-provider" ? CLAUDE_MODEL_SELECTION : CODEX_MODEL_SELECTION; const code = - scenario === "raw-error" || scenario === "incidental-mention" + scenario === "raw-error" ? "other" : scenario === "other-error" ? "serverOverloaded" : "invalid_encrypted_content"; const registry = ProviderAdapterRegistry.layerFromAdapters([ makeTestAdapter({ - instanceId: modelSelection.instanceId, - driver: scenario === "other-provider" ? CLAUDE_DRIVER : CODEX_DRIVER, - capabilities: - scenario === "other-provider" - ? ClaudeProviderCapabilitiesV2 - : CodexProviderCapabilitiesV2, - modelSelection, + instanceId: CODEX_MODEL_SELECTION.instanceId, + driver: CODEX_DRIVER, + capabilities: CodexProviderCapabilitiesV2, + modelSelection: CODEX_MODEL_SELECTION, capturedTurns, injectedHistory, nativeThreadGeneration: generation, @@ -1439,9 +1429,7 @@ describe("orchestration v2 provider switching", () => { message: "Encrypted content could not be verified", }, }) - : scenario === "incidental-mention" - ? "Transport failed; this is not invalid_encrypted_content." - : "Encrypted content could not be verified", + : "Encrypted content could not be verified", class: "provider_error", }), responseByRunOrdinal: { 1: "BASELINE_OK", 3: "RECOVERY_OK", 4: "FOLLOWUP_OK" }, @@ -1463,7 +1451,7 @@ describe("orchestration v2 provider switching", () => { creationSource: "web", text, attachments: [], - modelSelection, + modelSelection: CODEX_MODEL_SELECTION, dispatchMode: { type: "start_immediately" }, }); yield* orchestrator.streamStoredEvents.pipe( @@ -1486,7 +1474,7 @@ describe("orchestration v2 provider switching", () => { createdBy: "user", creationSource: "web", title: "Rejected Codex history", - modelSelection, + modelSelection: CODEX_MODEL_SELECTION, runtimeMode: "full-access", interactionMode: "default", branch: null, @@ -1501,7 +1489,7 @@ describe("orchestration v2 provider switching", () => { assert.isTrue( failed.turnItems.some((item) => item.type === "error" && item.failure.code === code), ); - if (scenario === "detached" || scenario === "legacy") { + if (scenario === "legacy") { yield* orchestrator.dispatch({ type: "provider-session.detach", commandId: CommandId.make("rejected:detach"), @@ -1523,19 +1511,6 @@ describe("orchestration v2 provider switching", () => { ), }); } - if (scenario === "interrupted" || scenario === "cancelled") { - yield* (yield* EventSink.EventSinkV2).write({ - events: [ - { - id: EventId.make(`rejected:${scenario}`), - type: "run.updated", - threadId, - occurredAt: failed.thread.createdAt, - payload: { ...failed.runs.at(-1)!, status: scenario }, - }, - ], - }); - } if (scenario === "repaired-native") { const providerThread = failed.providerThreads[0]!; yield* (yield* EventSink.EventSinkV2).write({ @@ -1564,12 +1539,7 @@ describe("orchestration v2 provider switching", () => { assert.lengthOf(recovered.contextHandoffs, 0); return; } - if ( - scenario === "other-error" || - scenario === "incidental-mention" || - scenario === "other-provider" || - scenario === "background" - ) { + if (scenario === "other-error" || scenario === "background") { assert.equal(recovered.runs.at(-1)?.status, "failed"); assert.equal(yield* Ref.get(generation), 1); assert.lengthOf(yield* Ref.get(capturedTurns), scenario === "background" ? 2 : 3);