Skip to content

Studio preview server dies on any uncaught exception, surfacing as "Connection lost" #3373

Description

@miguel-heygen

Describe the bug

The Studio preview server shares the one-shot CLI's process-wide uncaughtException
handler, which calls process.exit(1). For a render invocation that is correct — the
command is over anyway. For a long-running server it means any uncaught error anywhere
in the process tears down the server, dropping every open SSE connection at once.

Every Studio client watching a render then shows:

Connection lost. Is the render server running?

which blames the user's setup for something that was a crash inside our own process.

The chain, verified in source

1. The handler is process-wide and fatalpackages/cli/src/cli.ts:393:

process.on("uncaughtException", (error) => {
  if ((error as NodeJS.ErrnoException).code === "EPIPE") { ... }
  if (isRenderSucceeded()) {
    exitAfterPostRenderTermination("uncaughtException", "uncaught_exception", error);
  }
  exitAfterCliFailure("uncaught_exception", error);   // -> process.exit(1)
});

2. The only escape hatch is never set by the server. markRenderSucceeded() is called
from exactly two places, both inside the one-shot render command —
packages/cli/src/commands/render.ts:748 and :913. The preview subcommand that hosts
Studio never calls it, so isRenderSucceeded() is permanently false there and every
uncaught error takes the exitAfterCliFailure branch.

3. Studio reports the resulting socket drop as a connectivity problem
packages/studio/src/components/renders/useRenderQueue.ts:259 sets
error: "Connection lost. Is the render server running?" when the EventSource errors on a
job still in rendering.

Note the asymmetry with the normal failure path: a render job that fails cleanly is caught
in packages/cli/src/server/studioServer.ts and delivered as a graceful status: "failed"
SSE event carrying the real error text. Only a process death produces the generic
"Connection lost" wording. So the message a user sees tells you which of the two happened,
and this one means we exited.

Why this matters beyond the wording

A one-shot command exiting on an uncaught exception loses nothing. A server doing it loses
every concurrent job and every connected client, and the users who hit it are told to check
whether they started the server — which they did.

It also makes the failure look reproducible from the user's side ("happens every time")
while being invisible from ours, because the thing that died left no per-job error behind.

What is verified and what is not

Verified: the handler, the missing exemption for the preview server, the process.exit(1),
and the Studio error string. Those are facts about the code as it stands.

Not verified: that this is what produced any particular field report. Chasing the
suspected trigger — a browser target dying mid-capture — did not reproduce: 0 failures in
48 trials
of a 1050-frame composition across --workers 1 --low-memory-mode,
--no-low-memory-mode with default workers, and --workers 4, on both macOS and Linux. By
rule of three that puts the true crash rate under roughly 6%, consistent with "rare" and not
with "reliably reproducible". So no live escape-path evidence was captured, and no specific
unawaited listener has been identified.

That does not change the recommendation, because the defect here is structural rather than
dependent on any one trigger.

Suggested fix

Do not let a render job's uncaught error kill the process that is also serving the SSE
connection reporting on it. Either:

  • isolate the render-job promise chain in the preview server so its failures cannot reach
    the global handler, or
  • exempt the preview server from the fatal branch the same way isRenderSucceeded() already
    exempts a completed CLI render, logging and failing the affected job instead of exiting.

The second is closer to the existing design and reuses machinery that is already there.

Either way the outcome should be that a crashed render fails that job with its real error,
and every other client stays connected.

Environment

hyperframes  0.8.4
Affected     packages/cli/src/cli.ts:393
             packages/cli/src/commands/render.ts:748,913 (only markRenderSucceeded callers)
             packages/studio/src/components/renders/useRenderQueue.ts:259

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions