heygen-com/hyperframes · error · AmbiguousPreviewServerError

Multiple Studio preview servers match this project (${ports.

Error message

Multiple Studio preview servers match this project (${ports.join(", ")}). Pass --port <port> to choose one.

What it means

Thrown as AmbiguousPreviewServerError when more than one embedded Studio preview server matches the project directory and no --port was specified. The CLI cannot safely auto-select one, so it lists the matching ports and asks the user to disambiguate with --port.

Source

Thrown at packages/cli/src/utils/studioSelectionClient.ts:87

  const normalizedProjectDir = normalizePath(projectDir);
  const servers = await scan(startPort);
  const embeddedServers = servers.filter(
    (server) => normalizePath(server.projectDir) === normalizedProjectDir,
  );
  if (options.preferredPort !== undefined) {
    const preferred = embeddedServers.find((server) => server.port === options.preferredPort);
    if (preferred) return preferred;
    const viteServer = await findViteStudioServerForProject(normalizedProjectDir, fetchImpl, [
      options.preferredPort,
    ]);
    if (viteServer) return viteServer;
    if (embeddedServers.length > 0) {
      throw new PreviewServerPortMismatchError(options.preferredPort, embeddedServers);
    }
    return null;
  }
  if (embeddedServers.length === 1) return embeddedServers[0]!;
  if (embeddedServers.length > 1) throw new AmbiguousPreviewServerError(embeddedServers);
  return findViteStudioServerForProject(normalizedProjectDir, fetchImpl);
}

export function studioSelectionUrl(server: ActiveServer): string {
  return studioApiUrl(server, "selection");
}

export function studioApiUrl(server: ActiveServer, route: string): string {
  const host = server.host ?? "127.0.0.1";
  return `http://${host}:${server.port}/api/projects/${encodeURIComponent(server.projectName)}/${route}`;
}

// Vite dev servers bind IPv6 loopback (`::1`) by default while embedded servers
// bind IPv4 (`127.0.0.1`), so probe both — a single family misses the other and
// is exactly why `--selection`/`--context` failed against a local-studio preview.
const LOOPBACK_HOSTS = ["127.0.0.1", "[::1]"] as const;

async function findViteStudioServerForProject(

View on GitHub (pinned to c2996c8626)

Solutions

  1. Pass --port with one of the ports listed in the error to choose which server to use.
  2. Kill the stale preview server process (check with tman list or ps) and retry without --port.
  3. If both servers are intentional, always specify --port to disambiguate.

Example fix

// before: hyperframes lint   (multiple servers found)
// after:  hyperframes lint --port 3002
Defensive patterns

Strategy: validation

Validate before calling

import { scanActiveServers } from "../server/portUtils.js";

async function checkForDuplicateServers(projectDir: string): Promise<number[]> {
  const servers = (await scanActiveServers()).filter(
    (s) => normalizePath(s.projectDir) === normalizePath(projectDir),
  );
  return servers.map((s) => s.port);
}

Type guard

function isAmbiguousPreviewServer(err: unknown): err is AmbiguousPreviewServerError {
  return err instanceof AmbiguousPreviewServerError;
}

Try / catch

try {
  const server = await findPreviewServerForProject(dir);
} catch (err) {
  if (err instanceof AmbiguousPreviewServerError) {
    console.error(`Multiple servers on ports: ${err.ports.join(", ")}. Pass --port to choose.`);
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: Two or more Studio preview server processes are running for the same project directory on different ports (e.g. the user started preview twice, or a previous instance didn't shut down). Without --port, the CLI finds multiple embeddedServers and throws.

Common situations: Starting a second preview without killing the first; a zombie Studio process from a crashed session; running preview in two terminal windows for the same project.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/28f2516508f7fdeb. Report an issue: GitHub.