{"record":{"id":"199cb48ba0994c0f","repo":"can1357/oh-my-pi","slug":"retry-fallback-model-not-found-selector-raw","errorCode":null,"errorMessage":"Retry fallback model not found: ${selector.raw}","messagePattern":"Retry fallback model not found: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/turn-recovery.ts","lineNumber":1705,"sourceCode":"\t\tconst onAbort = () => aborted.resolve(false);\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\ttry {\n\t\t\treturn await Promise.race([confirmer(confirmation, signal), aborted.promise]);\n\t\t} finally {\n\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t}\n\t}\n\n\tasync applyRetryFallbackCandidate(\n\t\trole: string,\n\t\tselector: RetryFallbackSelector,\n\t\tcurrentSelector: string,\n\t\toptions?: { pinFallback?: boolean; apiKey?: string; signal?: AbortSignal },\n\t): Promise<boolean> {\n\t\tconst resolved = resolveModelOverride([selector.raw], this.#host.modelRegistry, this.#host.settings);\n\t\tconst candidate = resolved.model ?? this.#host.modelRegistry.find(selector.provider, selector.id);\n\t\tif (!candidate) {\n\t\t\tthrow new Error(`Retry fallback model not found: ${selector.raw}`);\n\t\t}\n\t\tconst apiKey =\n\t\t\toptions?.apiKey ??\n\t\t\t(await this.#host.modelRegistry.getApiKey(candidate, this.#host.sessionId(), { signal: options?.signal }));\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(`No API key for retry fallback ${selector.raw}`);\n\t\t}\n\t\tif (options?.signal?.aborted) return false;\n\n\t\t// Capture the configured selector (auto-aware) so a fallback chain preserves\n\t\t// `auto` instead of collapsing it to the level it resolved to this turn.\n\t\tconst currentThinkingLevel = this.#host.configuredThinkingLevel();\n\t\tconst requestedThinkingLevel = selector.thinkingLevel ?? currentThinkingLevel;\n\t\t// A fallback selector's explicit level (or the carried level after the\n\t\t// replacement model's floor clamp) must never exceed the session's\n\t\t// per-spawn effort ceiling.\n\t\tconst nextThinkingLevel =\n\t\t\trequestedThinkingLevel === AUTO_THINKING","sourceCodeStart":1687,"sourceCodeEnd":1723,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/turn-recovery.ts#L1687-L1723","documentation":"TurnRecovery's retry-fallback path resolves a model selector string (e.g. 'provider/model-id') via resolveModelOverride and the model registry, then throws if neither produces a candidate model. This means the selector the caller asked to fall back to does not match any model known to the registry (including any override/pinning logic). It is thrown before any API call is attempted.","triggerScenarios":"Calling the TurnRecovery retry-fallback method with options.pinFallback or a selector whose raw string does not resolve: misspelled model id, model removed/renamed in the registry, or a provider whose models were not loaded/available.","commonSituations":"A saved session or config references a model id from an older version of the catalog; user hand-edits a fallback selector; provider plugin not registered so its models are absent from the registry; typo in provider name.","solutions":["Verify the selector string matches an available model (run the same resolveModelOverride/registry lookup interactively or list registry models).","Correct the fallback selector in the session/config that supplied it (typos, old ids).","Ensure the provider's models are registered/loaded before the retry path runs (check registry.getAvailable()).","If resolveModelOverride returns nothing because the override points at a removed model, clear the stale override."],"exampleFix":"// before\nawait recovery.retryWithFallback({ raw: \"anthropic/claude-3-5-sonet\" });\n// after\nawait recovery.retryWithFallback({ raw: \"anthropic/claude-3-5-sonnet\" }); // id that exists in the registry","handlingStrategy":"validation","validationCode":"const resolved = resolveModelOverride([selector.raw], registry, settings);\nconst candidate = resolved.model ?? registry.find(selector.provider, selector.id);\nif (!candidate) throw new Error(`Fallback model unavailable: ${selector.raw}`);\n","typeGuard":"function isKnownModel(registry: ModelRegistry, provider: string, id: string): boolean {\n  return registry.find(provider, id) !== undefined;\n}","tryCatchPattern":"try {\n  await recovery.retryWithFallback(selector, opts);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Retry fallback model not found\")) {\n    // fall back to the primary model or surface a config error to the user\n  } else throw err;\n}","preventionTips":["Validate model selectors against the registry at config-load time.","Use autocomplete/managed pickers instead of hand-typed model ids.","Keep the model catalog updated so renamed ids still resolve.","Log the list of available models when resolution fails to speed diagnosis."],"tags":["model-registry","configuration","fallback"],"backgroundTag":"model-not-found","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}