heygen-com/hyperframes · error · Error

background preview did not stop for ${resolve(projectDir)}

Error message

background preview did not stop for ${resolve(projectDir)}

What it means

Thrown by stopBackgroundPreview when the previously-spawned preview server is still reported by scanActiveServers after 25 polling attempts (100ms apart). The pid was signalled (dependencies.kill / stopProcess) but the server keeps answering, so the session can't be considered stopped.

Source

Thrown at packages/cli/src/commands/previewLifecycle.ts:280

  const server = matchingServer(await scan(scanStart), projectDir);
  // A saved PID can be reused after a crashed preview, so only trust it while
  // a currently reachable server proves this exact project is still running.
  const pid = Number(server ? (server.pid ?? saved?.pid) : undefined);
  if (!Number.isInteger(pid) || pid <= 0) {
    removePreviewSession(projectDir, stateHome);
    return false;
  }

  (dependencies.kill ?? stopProcess)(pid);
  const sleep = dependencies.sleep ?? delay;
  for (let attempt = 0; attempt < 25; attempt++) {
    if (!matchingServer(await scan(scanStart), projectDir)) {
      removePreviewSession(projectDir, stateHome);
      return true;
    }
    await sleep(100);
  }
  throw new Error(`background preview did not stop for ${resolve(projectDir)}`);
}

View on GitHub (pinned to c2996c8626)

Solutions

  1. Find and kill the holder of the port manually: `lsof -i :<port>` / `netstat` then kill
  2. Run `hyperframes preview --stop` again or use `--force`
  3. Check the saved session (readPreviewSession) for a stale pid and clear the state file
  4. Raise permissions / run as the same user that started the server
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await stopBackgroundPreview(projectDir, startPort);
} catch (e) {
  if (e.message.includes("did not stop")) {
    // fall back to a direct port-holder kill, then re-scan
  }
  throw e;
}

Prevention

When it happens

Trigger: stopBackgroundPreview kills the pid then loops up to 25 times checking matchingServer; if it never disappears within ~2.5s the throw fires at previewLifecycle.ts:280.

Common situations: The pid belonged to a process that already exited and a different process reused the port; the kill signal is being ignored or the process is stuck in a non-interruptible state; a second preview server is also bound to the scanned port range; permissions prevent killing the owner.

Related errors


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