diff --git a/src/codex/subagent-model-fallback.ts b/src/codex/subagent-model-fallback.ts index 27eea1e53d..ddd5b67e60 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 { hasOwnProvider } from "../config"; +import { DEFAULT_SUBAGENT_MODELS, hasOwnProvider } from "../config"; import { isRateLimitOrQuotaFailureMessage } from "../lib/errors"; import type { OcxParsedRequest, OcxConfig } from "../types"; import { slugsEquivalent } from "../providers/slug-codec"; @@ -609,9 +609,14 @@ export function applySubagentModelFallback( resolvedFallbackChain?: readonly string[] | null, ): { from?: string; to?: string; skipped?: string[] } | null { if (!isThreadSpawnRequest(headers)) return null; - const fallbackChain = resolvedFallbackChain === undefined + const configuredFallbackChain = 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 75b2359ad5..d600072798 100644 --- a/tests/subagent-model-fallback.test.ts +++ b/tests/subagent-model-fallback.test.ts @@ -1056,6 +1056,29 @@ 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 = {