{"record":{"id":"42cedd829a6b92cb","repo":"nexu-io/open-design","slug":"model-model-is-not-registered-for-surface","errorCode":null,"errorMessage":"model \"${model}\" is not registered for surface \"${where}\". Allowed: ${ids}.","messagePattern":"model \"(.+?)\" is not registered for surface \"(.+?)\"\\. Allowed: (.+?)\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/media/index.ts","lineNumber":432,"sourceCode":"                : [],\n      };\n    } else {\n      throw new Error(\n        `unknown model: ${model}. Pass --model from the registered list (see /api/media/models), ` +\n        `or pass a full fal-ai/* path (e.g. fal-ai/flux/dev) for any Fal model.`,\n      );\n    }\n  }\n  // Reject cross-surface combinations for catalogued models.\n  const resolvedAudioKind =\n    surface === 'audio' ? audioKind || 'music' : undefined;\n  if (!isFalCustomPath && !isCatalogBypass) {\n    const allowed = modelsForSurface(surface, resolvedAudioKind);\n    if (!allowed.some((m) => m.id === def.id)) {\n      const ids = allowed.map((m) => m.id).join(', ');\n      const where =\n        surface === 'audio' ? `audio · ${resolvedAudioKind}` : surface;\n      throw new Error(\n        `model \"${model}\" is not registered for surface \"${where}\". Allowed: ${ids}.`,\n      );\n    }\n  }\n\n  // Clamp registry-bound numeric inputs to their allowed buckets so a\n  // hallucinated --length 9999999 doesn't reach a real provider as-is\n  // when stubs are swapped for paid integrations.\n  const lengthClamp =\n    surface === 'video'\n      ? def.provider === 'vela'\n        ? {\n            value:\n              typeof length === 'number' && Number.isFinite(length)\n                ? length\n                : undefined,\n            warning: null,\n          }","sourceCodeStart":414,"sourceCodeEnd":450,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/media/index.ts#L414-L450","documentation":"Thrown after successful model lookup when the resolved catalog model is not in the allowed list for the requested surface (and, for audio, the resolved audioKind). modelsForSurface(surface, resolvedAudioKind) returns the per-surface allow-list; if def.id is absent the dispatcher refuses to render. This is the cross-surface guard for catalogued models — fal-ai/* and aihubmix-* bypasses are exempt because their defs are synthesized with the caller's surface.","triggerScenarios":"Using an image-only model with --surface video (or vice versa); using a TTS model that is registered only for audioKind=speech while passing --audio-kind music; resolving audio without a kind (defaults to 'music') for a speech-only model.","commonSituations":"Agent picks a familiar model id without checking which surface it belongs to; UI lets the user pair any model with any surface; user assumes 'dall-e-3' works for video because it is a famous media model.","solutions":["Read the 'Allowed' list in the error and switch --model to one of those ids.","Switch --surface to the surface the chosen model is registered for.","For audio, set --audio-kind to the kind the model supports (speech TTS models are usually speech-only).","If you genuinely need an arbitrary Fal model on a different surface, use the fal-ai/* bypass form, which skips this guard."],"exampleFix":"// before\nod media generate --surface audio --audio-kind music --model gpt-4o-mini-tts --prompt \"...\"\n// after\nod media generate --surface audio --audio-kind speech --model gpt-4o-mini-tts --prompt \"...\"","handlingStrategy":"validation","validationCode":"function modelAllowedForSurface(modelId: string, surface: 'image'|'video'|'audio', audioKind: 'music'|'speech'|'sfx'|undefined, allowed: {id:string}[]): boolean {\n  const resolved = surface === 'audio' ? (audioKind || 'music') : undefined;\n  // mirror dispatcher logic: skip for fal-ai/* and aihubmix-* bypasses\n  if (/^fal-ai\\//.test(modelId) || /^aihubmix-/.test(modelId)) return true;\n  return allowed.some(m => m.id === modelId);\n}","typeGuard":"function isModelForSurface(def: {id:string}, surface: string, allowed: {id:string}[]): boolean {\n  return allowed.some(m => m.id === def.id);\n}","tryCatchPattern":null,"preventionTips":["Drive surface selection from the model's registered caps rather than letting the user pick a surface independently.","For audio, pair the model with the audioKind it was registered under.","Surface the allowed list from /api/media/models filtered by surface in the UI/agent prompt."],"tags":["validation","media","cross-surface","catalog"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}