nexu-io/open-design · error · Error
unsupported surface: ${surface}
Error message
unsupported surface: ${surface} What it means
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.
Source
Thrown at apps/daemon/src/media/index.ts:368
voice,
audioKind,
language,
loop,
promptInfluence,
compositionDir,
image,
requestInit,
workspaceId,
onProviderRequestSettled,
} = args;
if (!projectRoot) throw new Error('projectRoot required');
if (!projectsRoot) throw new Error('projectsRoot required');
if (typeof projectId !== 'string' || !projectId) {
throw new Error('projectId required');
}
if (!SURFACES.has(surface)) {
throw new Error(`unsupported surface: ${surface}`);
}
if (typeof model !== 'string' || !model) {
throw new Error('model required');
}
if (surface === 'audio' && audioKind && !AUDIO_KINDS.has(audioKind)) {
throw new Error(
`unsupported audioKind: ${audioKind}. Allowed: music | speech | sfx.`,
);
}
// Arbitrary fal.ai model paths (e.g. "fal-ai/flux/dev") bypass the
// catalog so users can reach any model on fal without waiting for a
// catalog entry. Surface comes from the caller; no cross-surface guard
// is needed because the fal renderer reads ctx.surface directly.
let def = findMediaModel(model);
let isFalCustomPath = false;
let isCatalogBypass = false;
if (!def) {
if (/^fal-ai\//.test(model)) {View on GitHub (pinned to 5be4028344)
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.
Example fix
// before
{ surface: "music", model: "..." }
// after
{ surface: "audio", audioKind: "music", model: "..." } Defensive patterns
Strategy: type-guard
Validate before calling
const SURFACES = new Set(['image', 'video', 'audio']);
if (!SURFACES.has(surface)) throw new Error(`unsupported surface: ${surface}`); Type guard
function isMediaSurface(v: unknown): v is 'image' | 'video' | 'audio' {
return v === 'image' || v === 'video' || v === 'audio';
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Confusing surface with audioKind (passing "music" as surface); agent hallucinating a surface name; version skew between client and daemon.
Related errors
- resume must be a boolean
- inputs must be an object
- artifactManifest must be an object
- --image path "${rel}" resolves outside the project directory
- --image not found: ${rel}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/72a487167c428969.
Report an issue: GitHub.