From 79592fc6c1f18b7c52ab571576bd374bf23413d7 Mon Sep 17 00:00:00 2001 From: jun Date: Wed, 2 Sep 2026 11:07:43 +0900 Subject: [PATCH 1/2] Revert "fix(subagents): let encrypted-task recovery run before the synthesized native chain (#3240)" This reverts commit 7f00d0eee917dc6ec08e688eebd7ad4f3d972416. --- src/codex/subagent-model-fallback.ts | 8 -------- tests/subagent-model-fallback.test.ts | 27 --------------------------- 2 files changed, 35 deletions(-) diff --git a/src/codex/subagent-model-fallback.ts b/src/codex/subagent-model-fallback.ts index 7110d4cf50..ddd5b67e60 100644 --- a/src/codex/subagent-model-fallback.ts +++ b/src/codex/subagent-model-fallback.ts @@ -614,15 +614,7 @@ export function applySubagentModelFallback( : resolvedFallbackChain; // Native-only encrypted V2 tasks need a readable ChatGPT backend even when the // operator configured no fallback chain. Keep ordinary routed spawns unchanged. - // - // Not when encrypted-task recovery is enabled: that operator chose to decrypt the - // assignment and stay on the routed model. The synthesized chain would reroute the - // spawn to native in this first pass, before recovery runs, and recovery's own - // caller-auth / proxy-secret / token-validity gates would never execute - // (tests/agent-task-recovery-security.test.ts went 13/13 -> 2/13 when #3239 landed - // without this guard). A configured chain keeps its existing precedence. const fallbackChain = configuredFallbackChain === null && nativeFallbackOnly - && config.agentTaskRecovery?.enabled !== true ? normalizedChain(parsed.modelId, config, [], DEFAULT_SUBAGENT_MODELS) : configuredFallbackChain; if (!fallbackChain) return null; diff --git a/tests/subagent-model-fallback.test.ts b/tests/subagent-model-fallback.test.ts index 510c294d80..d600072798 100644 --- a/tests/subagent-model-fallback.test.ts +++ b/tests/subagent-model-fallback.test.ts @@ -1079,33 +1079,6 @@ describe("subagent model fallback chain", () => { expect(parsed.modelId).toBe("gpt-5.5"); }); - test("the synthesized native chain yields to enabled encrypted-task recovery", () => { - // With recovery on, the first fallback pass must leave the routed spawn alone so - // recoverEncryptedAgentTask runs (and its security gates fire); rerouting here - // would bypass them. - const config = cfg({ - subagentModelFallback: undefined, - defaultProvider: "xai", - agentTaskRecovery: { enabled: true }, - }); - const parsed = { - modelId: "xai/grok-4.5", - options: {}, - context: { messages: [] }, - _rawBody: { model: "xai/grok-4.5" }, - }; - const result = applySubagentModelFallback( - parsed as never, - new Headers({ "x-openai-subagent": "collab_spawn" }), - config, - "pool-a", - Date.now(), - true, - ); - expect(result).toBeNull(); - expect(parsed.modelId).toBe("xai/grok-4.5"); - }); - test("applySubagentModelFallback is a no-op for main turns", () => { updateAccountQuota("pool-a", 95); const parsed = { From 39a71cc6972210054e45b171b2360cfecb855388 Mon Sep 17 00:00:00 2001 From: jun Date: Wed, 2 Sep 2026 11:07:43 +0900 Subject: [PATCH 2/2] Revert "fix(subagents): auto-fallback encrypted V2 spawns to native Codex without a configured chain (#3239)" This reverts commit 744d12d02d8078980a1e195a18be4485e30f4d4a. --- src/codex/subagent-model-fallback.ts | 9 ++------- tests/subagent-model-fallback.test.ts | 23 ----------------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/src/codex/subagent-model-fallback.ts b/src/codex/subagent-model-fallback.ts index ddd5b67e60..27eea1e53d 100644 --- a/src/codex/subagent-model-fallback.ts +++ b/src/codex/subagent-model-fallback.ts @@ -7,7 +7,7 @@ */ import { existsSync, readdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; -import { DEFAULT_SUBAGENT_MODELS, hasOwnProvider } from "../config"; +import { hasOwnProvider } from "../config"; import { isRateLimitOrQuotaFailureMessage } from "../lib/errors"; import type { OcxParsedRequest, OcxConfig } from "../types"; import { slugsEquivalent } from "../providers/slug-codec"; @@ -609,14 +609,9 @@ export function applySubagentModelFallback( resolvedFallbackChain?: readonly string[] | null, ): { from?: string; to?: string; skipped?: string[] } | null { if (!isThreadSpawnRequest(headers)) return null; - const configuredFallbackChain = resolvedFallbackChain === undefined + const fallbackChain = resolvedFallbackChain === undefined ? resolveSubagentFallbackChain(parsed, config) : resolvedFallbackChain; - // Native-only encrypted V2 tasks need a readable ChatGPT backend even when the - // operator configured no fallback chain. Keep ordinary routed spawns unchanged. - const fallbackChain = configuredFallbackChain === null && nativeFallbackOnly - ? normalizedChain(parsed.modelId, config, [], DEFAULT_SUBAGENT_MODELS) - : configuredFallbackChain; if (!fallbackChain) return null; const selection = selectAvailableSubagentModel( parsed.modelId, diff --git a/tests/subagent-model-fallback.test.ts b/tests/subagent-model-fallback.test.ts index d600072798..75b2359ad5 100644 --- a/tests/subagent-model-fallback.test.ts +++ b/tests/subagent-model-fallback.test.ts @@ -1056,29 +1056,6 @@ describe("subagent model fallback chain", () => { expect((parsed._rawBody as { model?: string }).model).toBe("alibaba-token-plan/qwen3.8-max"); }); - test("encrypted routed spawn gets an automatic native fallback when none is configured", () => { - const config = cfg({ - subagentModelFallback: undefined, - defaultProvider: "xai", - }); - const parsed = { - modelId: "xai/grok-4.5", - options: {}, - context: { messages: [] }, - _rawBody: { model: "xai/grok-4.5" }, - }; - const result = applySubagentModelFallback( - parsed as never, - new Headers({ "x-openai-subagent": "collab_spawn" }), - config, - "pool-a", - Date.now(), - true, - ); - expect(result?.to).toBe("gpt-5.5"); - expect(parsed.modelId).toBe("gpt-5.5"); - }); - test("applySubagentModelFallback is a no-op for main turns", () => { updateAccountQuota("pool-a", 95); const parsed = {