Skip to content

fix(cli): stop a caught post-render throw reporting a valid render as failed - #3409

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/render-exit1-unguarded-trial-write
Aug 22, 2026
Merged

miguel-heygen merged 1 commit into
mainfrom
fix/render-exit1-unguarded-trial-write

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

A render that produced and validated its artifact could still exit 1. finalizeCli now
sanitizes the exit code once a render has validated, so a caught teardown throw cannot
report a good render as a failure.

Why

From the field on 0.8.7: the MP4 was on disk, an independent ffprobe and a full decode
both passed, and the CLI exited 1 immediately after logging artifact validated.

This shape is already known — render-success-state.ts exists solely for it and documents
three earlier cases — so the sentinel was in place. The gap is which paths read it:

path consults isRenderSucceeded()
uncaughtException handler yes
unhandledRejection handler yes
a throw the command wrapper catches no

A caught post-render throw never reaches either handler. It becomes an ordinary non-zero
CommandResult, and finalizeCli wrote that straight to process.exitCode.

That left a run disagreeing with itself. commandSucceededForTelemetry() already lets a
validated render override a failure, so telemetry recorded the run as a success while the
shell saw exit 1.

How

One line of policy in finalizeCli, where every command result funnels through:

const exitCode = isRenderSucceeded() ? 0 : result.exitCode;

used for commandFailed, the telemetry record, and process.exitCode, so the three cannot
disagree.

Deliberately not wrapping the individual teardown steps. Which step threw does not
matter; that the artifact is committed does. Wrapping N call sites would leave the N+1th
unguarded, which is how this reached the field in the first place. The throw is still
printed, so it stays visible for diagnosis without being fatal.

Test plan

Reproduced as a failing test before fixing, and on macOS — this is not Windows-specific,
the field reports are one instance of it.

Before:

× CLI lifecycle > does not let a post-validation throw doom a render whose artifact is on disk
  → expected 1 to be +0

After: 8 passed in cli.lifecycle.test.ts, and the full CLI package is green at
2812 passed / 3 skipped across 193 files.

Two tests, one per side of the invariant:

  • a command that marks the render validated and then throws now exits 0, and reports
    success: true, exitCode: 0 to telemetry;
  • a command that throws with no validated render still exits non-zero and reports
    success: false — the guard that stops the sanitizer swallowing genuine failures.

Not covered

  • The reporter also saw orphan chrome-headless-shell trees from earlier interrupted runs.
    That is a separate cleanup problem and is untouched here; this PR only stops a delivered
    render being scored as failed.
  • I did not identify which specific teardown step threw on their machine. The fix is
    deliberately independent of that — it holds for any of them — but it does mean the
    underlying throw is still occurring and will now surface as a printed warning rather than
    a failed exit.

… failed

A render that produced and validated its artifact still exited 1. Reported
again from the field on 0.8.7: the MP4 was on disk and an independent ffprobe
and full decode both passed, and the CLI exited 1 immediately after logging
`artifact validated`.

`render-success-state.ts` exists for exactly this and documents three earlier
cases, so the sentinel was already there. Its gap is which paths read it: the
uncaughtException and unhandledRejection handlers both consult
`isRenderSucceeded()`, but a post-render throw that the command wrapper CATCHES
never reaches either. It becomes an ordinary non-zero CommandResult, and
`finalizeCli` wrote that straight to `process.exitCode`.

The result was a run that disagreed with itself: `commandSucceededForTelemetry()`
already lets a validated render override a failure, so telemetry recorded
success while the shell saw exit 1.

Sanitize once in `finalizeCli`, where every command result funnels through,
rather than wrapping the individual steps. Which step threw does not matter;
that the artifact is committed does. The throw is still printed, so it stays
visible for diagnosis without being fatal.

Reproduced first as a failing test (`expected 1 to be +0`) on macOS, so this is
not Windows-specific — the field reports are one instance of it. A second test
pins the other side: a command that throws with no validated render still exits
non-zero, so the sanitizer cannot swallow a genuine failure.
@miguel-heygen
miguel-heygen merged commit ea95b7d into main Aug 22, 2026
47 of 48 checks passed
@miguel-heygen
miguel-heygen deleted the fix/render-exit1-unguarded-trial-write branch August 22, 2026 01:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant