{"record":{"id":"379c4d4e52a29e1e","repo":"different-ai/openwork","slug":"selected-model-is-unavailable-choose-another-mode","errorCode":null,"errorMessage":"Selected model is unavailable. Choose another model before sending.","messagePattern":"Selected model is unavailable\\. Choose another model before sending\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/app/src/react-app/shell/session-route.tsx","lineNumber":1275,"sourceCode":"          openSettings: handleOpenSettings,\n        });\n      },\n      onSendDraft: async (draft: ComposerDraft, sessionId: string): Promise<CloudMcpSubmissionResult> => {\n        const targetSessionId = sessionId.trim() || selectedSessionId;\n        if (!targetSessionId) return { outcome: \"cancelled\", reason: \"context_changed\" };\n        const text = (draft.resolvedText ?? draft.text).trim();\n        if (!text && draft.attachments.length === 0) {\n          return { outcome: \"cancelled\", reason: \"context_changed\" };\n        }\n        // Per-conversation model memory: a session that picked its own model\n        // sends with it (and its variant) instead of the global default.\n        const sessionModelSelection = getSessionModelSelection(targetSessionId);\n        const sendModel = sessionModelSelection?.model ?? local.prefs.defaultModel;\n        const sendVariant = sessionModelSelection ? sessionModelSelection.variant : modelVariantValue;\n        // Send-time validation targets the exact provider/model identity this\n        // conversation displays and will submit — not the global default.\n        if (resolveModelAvailability(sendModel ?? null).status === \"unavailable\") {\n          throw new Error(\"Selected model is unavailable. Choose another model before sending.\");\n        }\n\n        return submitWithCloudMcpReadiness({\n          // Temporarily bypass the pre-send Cloud MCP gate: it blocks every\n          // message, including tasks that do not use connected services.\n          skipGate: true,\n          send: async () => {\n            await sendWithRevertRollback({\n              revertMessageId: draft.revertMessageId,\n              abort: () => abortSessionSafe(opencodeClient, targetSessionId, selectedWorkspaceRoot || undefined, {\n                source: \"session.edit_resend.before_revert\",\n                initiator: \"user\",\n                reason: \"abort active run before replacing a reverted message\",\n              }),\n              revert: async (messageId) => {\n                const reverted = await revertSession(opencodeClient, targetSessionId, messageId);\n                applySessionRevert(selectedWorkspaceId, reverted);\n              },","sourceCodeStart":1257,"sourceCodeEnd":1293,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/app/src/react-app/shell/session-route.tsx#L1257-L1293","documentation":"Before sending a message, the send path validates the exact model identity this conversation will submit (session-scoped selection, falling back to the user's default model) with resolveModelAvailability(). If that model's status is 'unavailable' — e.g. the provider is disconnected, the model was removed, or the API key is missing — the send is rejected with this error instead of failing server-side.","triggerScenarios":"Calling the send/submit handler when resolveModelAvailability(sendModel).status === 'unavailable': session model selection or defaultModel points to a provider/model that is disconnected, unauthenticated, or no longer in the model list.","commonSituations":"A provider API key was revoked or removed from settings; models.dev/provider catalog changed and an old model id persists in the session store; cloud inference was disabled; user switched from a local to a remote workspace whose endpoint lacks that model.","solutions":["Open the model picker for the conversation and choose an available model, then resend","Reconnect or re-authenticate the provider that owns the model (add/fix its API key in Settings → Models/Providers)","Clear the stale session model selection so it falls back to a valid defaultModel","Update the app/provider catalog if the model id changed upstream"],"exampleFix":"// before\nuseSessionModelStore.getState().setModel(sessionId, \"anthropic/claude-2.1\", null) // stale id\n// after\nuseSessionModelStore.getState().setModel(sessionId, resolveFirstAvailableModel(), null)","handlingStrategy":"validation","validationCode":"const availability = resolveModelAvailability(sendModel ?? null);\nif (availability.status === \"unavailable\") {\n  notify(`Model ${sendModel} is unavailable; pick another.`);\n  return;\n}\nawait send();","typeGuard":"const isModelAvailable = (m: string | null | undefined): m is string =>\n  !!m && resolveModelAvailability(m).status !== \"unavailable\";","tryCatchPattern":"try {\n  await send();\n} catch (e) {\n  if (String(e.message).startsWith(\"Selected model is unavailable\")) {\n    openModelPicker(targetSessionId);\n  }\n}","preventionTips":["Prune stale session model selections when the provider catalog refreshes","Re-validate saved defaultModel against availability on app start","Show availability badges in the model picker so dead models are obvious","Re-authenticate providers whose keys expired"],"tags":["ui","model-selection","validation","provider"],"backgroundTag":"selected-model-unavailable","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}