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

  1. Use one of the supported surfaces: "image", "video", or "audio".
  2. For music/speech/sfx, set `surface: "audio"` and `audioKind: "music" | "speech" | "sfx"`.
  3. Update the daemon and client to matching versions if you expect a newer surface.
  4. 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

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


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/72a487167c428969. Report an issue: GitHub.