mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
fix(cli): don't override exit code after artifact validated
The render command's post-artifact-validated cleanup (telemetry flush,
feedback prompt, worker/browser teardown, stray promise rejections) can
throw AFTER the producer has committed a valid MP4 to disk. Field signal
ts=1784169760, ts=1784171150, ts=1784172467 (all win32/x64, CLI 0.7.58,
ffmpeg=no, 1080x1920): ffprobe + visual QA confirmed the outputs are
valid, but the CLI exited 1 after the terminal "artifact validated" log
with no final error message.
Introduce a `renderSucceeded` sentinel that flips after `executeRenderJob`
(or the Docker child render) resolves cleanly. From that point on:
- Post-render steps in the render command (trackRenderMetrics,
printRenderComplete, warnIfWebmAlphaDropped, maybePromptRenderFeedback)
run through `runPostRenderStep`/`runPostRenderStepAsync` guards that
swallow throws, log a compact warning to stderr, and sanitize a stray
`process.exitCode` back to 0.
- The CLI's top-level `uncaughtException` handler logs the throw for
diagnosis but exits 0 instead of 1 when the render already succeeded.
- The CLI's `unhandledRejection` handler stops flipping `commandFailed`
(which drove the success:false telemetry field) when the render
already succeeded.
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
— Via
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Shared render-success sentinel + post-artifact-validated cleanup guards.
|
||||
*
|
||||
* Read by the top-level CLI process handlers (uncaughtException /
|
||||
* unhandledRejection) to sanitize the exit code when a post-artifact-validated
|
||||
* cleanup step throws.
|
||||
*
|
||||
* Set by the render command AFTER `executeRenderJob` (or the Docker child
|
||||
* render) resolves cleanly — the point at which the artifact has been
|
||||
* validated AND committed to disk. Any throw after this point (worker
|
||||
* teardown, browser shutdown, telemetry flush, feedback prompt, stray
|
||||
* promise rejection) must not turn a valid render into an exit-1
|
||||
* "no final error message" failure.
|
||||
*
|
||||
* Field signal (all win32/x64, CLI 0.7.58, ffmpeg=no, 1080x1920 renders):
|
||||
* - ts=1784169760 — 6-worker capture retried down after Runtime.evaluate
|
||||
* timeout, completed all 1260 frames, printed 'artifact validated',
|
||||
* exited 1 with no final error message. Output MP4 valid on disk.
|
||||
* - ts=1784171150 — full REPRO command provided; identical shape.
|
||||
* - ts=1784172467 — `--workers 2`, identical shape.
|
||||
* All three: ffprobe + visual QA confirmed the output was valid; the CLI
|
||||
* still exited 1 after the terminal "artifact validated" checkpoint.
|
||||
*/
|
||||
|
||||
let renderSucceeded = false;
|
||||
|
||||
/**
|
||||
* Called by the render command after the producer's `executeRenderJob` (or
|
||||
* the Docker child) resolves cleanly. From this point on, any thrown
|
||||
* teardown error must not be allowed to override the exit code.
|
||||
*/
|
||||
export function markRenderSucceeded(): void {
|
||||
renderSucceeded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read by cli.ts process handlers to decide whether a late throw is fatal.
|
||||
* When true, the handlers log the throw at warn level (so it's still visible
|
||||
* for diagnosis) but do not surface it as an exit-1 failure.
|
||||
*/
|
||||
export function isRenderSucceeded(): boolean {
|
||||
return renderSucceeded;
|
||||
}
|
||||
|
||||
/** Test-only reset. Not exported from the package. */
|
||||
export function _resetRenderSuccessForTests(): void {
|
||||
renderSucceeded = false;
|
||||
}
|
||||
|
||||
type PostRenderErrorSink = (message: string) => void;
|
||||
|
||||
const defaultErrorSink: PostRenderErrorSink = (message) => {
|
||||
process.stderr.write(`${message}\n`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a post-artifact-validated cleanup step so a throw cannot flip the CLI
|
||||
* exit code. `markRenderSucceeded()` MUST have been called first — this
|
||||
* helper is only safe on the success path where the artifact is already
|
||||
* committed to disk. Logs a compact warning to stderr, resets a stray
|
||||
* `process.exitCode` back to 0, and swallows the error.
|
||||
*/
|
||||
export function runPostRenderStep(
|
||||
label: string,
|
||||
fn: () => void,
|
||||
sink: PostRenderErrorSink = defaultErrorSink,
|
||||
): void {
|
||||
try {
|
||||
fn();
|
||||
} catch (err) {
|
||||
reportPostRenderStepFailure(label, err, sink);
|
||||
}
|
||||
}
|
||||
|
||||
export async function runPostRenderStepAsync(
|
||||
label: string,
|
||||
fn: () => Promise<void>,
|
||||
sink: PostRenderErrorSink = defaultErrorSink,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await fn();
|
||||
} catch (err) {
|
||||
reportPostRenderStepFailure(label, err, sink);
|
||||
}
|
||||
}
|
||||
|
||||
function reportPostRenderStepFailure(label: string, err: unknown, sink: PostRenderErrorSink): void {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
sink(` [hyperframes] Post-render step '${label}' failed (render already succeeded): ${message}`);
|
||||
// Guard against the failing step (or something it triggered) setting a
|
||||
// non-zero exitCode. The render succeeded → the CLI must exit 0.
|
||||
if (process.exitCode !== undefined && process.exitCode !== 0) {
|
||||
process.exitCode = 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user