{"record":{"id":"72a487167c428969","repo":"nexu-io/open-design","slug":"unsupported-surface-surface","errorCode":null,"errorMessage":"unsupported surface: ${surface}","messagePattern":"unsupported surface: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/media/index.ts","lineNumber":368,"sourceCode":"    voice,\n    audioKind,\n    language,\n    loop,\n    promptInfluence,\n    compositionDir,\n    image,\n    requestInit,\n    workspaceId,\n    onProviderRequestSettled,\n  } = args;\n\n  if (!projectRoot) throw new Error('projectRoot required');\n  if (!projectsRoot) throw new Error('projectsRoot required');\n  if (typeof projectId !== 'string' || !projectId) {\n    throw new Error('projectId required');\n  }\n  if (!SURFACES.has(surface)) {\n    throw new Error(`unsupported surface: ${surface}`);\n  }\n  if (typeof model !== 'string' || !model) {\n    throw new Error('model required');\n  }\n  if (surface === 'audio' && audioKind && !AUDIO_KINDS.has(audioKind)) {\n    throw new Error(\n      `unsupported audioKind: ${audioKind}. Allowed: music | speech | sfx.`,\n    );\n  }\n  // Arbitrary fal.ai model paths (e.g. \"fal-ai/flux/dev\") bypass the\n  // catalog so users can reach any model on fal without waiting for a\n  // catalog entry. Surface comes from the caller; no cross-surface guard\n  // is needed because the fal renderer reads ctx.surface directly.\n  let def = findMediaModel(model);\n  let isFalCustomPath = false;\n  let isCatalogBypass = false;\n  if (!def) {\n    if (/^fal-ai\\//.test(model)) {","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/media/index.ts#L350-L386","documentation":"Thrown by the media dispatch entry point when the `surface` argument is not in the SURFACES set (image, video, audio). Surface selects which media generation pipeline runs; an unknown value cannot be routed. The check precedes model/catalog resolution.","triggerScenarios":"Calling media generation with `surface: \"text\"`, `\"3d\"`, `\"music\"` (music is an audioKind, not a surface — surface must be \"audio\"), or any typo; passing a surface from a newer/older daemon version that the running daemon does not know.","commonSituations":"Confusing surface with audioKind (passing \"music\" as surface); agent hallucinating a surface name; version skew between client and daemon.","solutions":["Use one of the supported surfaces: \"image\", \"video\", or \"audio\".","For music/speech/sfx, set `surface: \"audio\"` and `audioKind: \"music\" | \"speech\" | \"sfx\"`.","Update the daemon and client to matching versions if you expect a newer surface.","Check the media catalog/MCP tool description for the current surface list."],"exampleFix":"// before\n{ surface: \"music\", model: \"...\" }\n// after\n{ surface: \"audio\", audioKind: \"music\", model: \"...\" }","handlingStrategy":"type-guard","validationCode":"const SURFACES = new Set(['image', 'video', 'audio']);\nif (!SURFACES.has(surface)) throw new Error(`unsupported surface: ${surface}`);","typeGuard":"function isMediaSurface(v: unknown): v is 'image' | 'video' | 'audio' {\n  return v === 'image' || v === 'video' || v === 'audio';\n}","tryCatchPattern":null,"preventionTips":["Use surface=\"audio\" with audioKind=\"music|speech|sfx\" for audio.","Keep client and daemon versions aligned for the surface list.","Validate surface in your tool schema as an enum."],"tags":["media","validation","surface","input-coercion"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}