Summary
On a production-scale, DOM-heavy composition under forced software raster (--no-browser-gpu, SwiftShader), the render ends with Streaming encode failed: FFmpeg exited with code 255 whose stderr tail reads Exiting normally, received signal 15. — twice, ~19.7 min and ~13.6 min end-to-end, no usable output. We have not reproduced this with synthetic compositions, and we do not attribute the terminating signal source. This is an observability/robustness request, not a claimed root-cause report.
Environment (pinned)
| component |
version |
| hyperframes CLI |
0.6.52 (npm, local node_modules/.bin/hyperframes) |
| Node.js |
24.19.0 |
| OS |
macOS 26.2 (25C56), Apple Silicon |
| Chrome (puppeteer-launched) |
152.0.7977.76 (system Chrome via PUPPETEER_EXECUTABLE_PATH) |
| ffmpeg |
8.1.2 |
Observed
Composition: single-document assembly (root + 3 bare-id scene wrappers), 1920x1080@30, 19 s = 570 frames, ~60 KB inline DOM. Forced software raster, two attempts, both exit 1:
- attempt 1: end-to-end ≈19.7 min; last ffmpeg progress line
frame=265 fps=0.5 ... elapsed=0:08:13 inside the error's stderr tail; final error Streaming encode failed: FFmpeg exited with code 255, tail ends Exiting normally, received signal 15.
- attempt 2: end-to-end ≈13.6 min; identical signature.
- External 10 s process sampling around both attempts: the Chrome browser process was alive in every sample until HyperFrames tore down; the process that received SIGTERM is the ffmpeg streaming-encoder child.
- GPU default mode on the same page: completes (~89 s incl. a 45 s player-ready wait — a separate topic, not chained causally here; the same warning preceded five successful renders and one no-warning control).
What isolation achieved: a canvas-driven, 570-frame, DOM-light repro under --no-browser-gpu finishes end-to-end in ≈115 s (≈5 s compile + 45 s unrelated wait + ≈65–70 s capture+encode, ~0.12 s/frame). So the abort appears tied to slow per-frame capture of a DOM-heavy page under software raster, not to frame count alone — and we could not shrink it further into a synthetic repro.
Unverified code reading, for triage only (not our diagnosis): in dist/cli.js (0.6.52), spawnStreamingEncoder (~L32025) attaches two SIGTERM paths to the ffmpeg child: an idle watchdog ffmpegStreamingTimeout (default 600 000 ms, env FFMPEG_STREAMING_TIMEOUT_MS) that is reset on writes Node reports as accepted, and the job-level abortSignal handler. Both could produce exactly "signal 15"; we cannot tell which one fired from outside the process. For completeness: the capture loop ignores writeFrame's boolean return — but in Node that return is Writable backpressure semantics (buffer threshold), not encoder liveness, and we deliberately do not propose treating false as fatal.
Asks
- Record the kill reason in the surfaced error (which watchdog/abort path fired, frames accepted vs attempted at kill time) so consumers don't have to read the bundle to enumerate suspects.
- Propagate encoder-child exit state (an explicit encoder
error/dead status), so the capture loop can stop early instead of spending the remaining capture budget against a terminated encoder — failure at ~19.7 min cost us the full render twice.
- Optional guidance: is there a recommended producer configuration for pages whose software-mode capture rate is ≤1 fps (e.g. the
enableChunkedEncode path that ships disabled by default)?
Package hygiene: redacted log excerpts available on request (consumer wrapper lines removed; scene ids replaced by host-a/b/c; terminal control sequences stripped, verified by scan with 0 remaining control bytes; no consumer-project paths or internal designations).
Summary
On a production-scale, DOM-heavy composition under forced software raster (
--no-browser-gpu, SwiftShader), the render ends withStreaming encode failed: FFmpeg exited with code 255whose stderr tail readsExiting normally, received signal 15.— twice, ~19.7 min and ~13.6 min end-to-end, no usable output. We have not reproduced this with synthetic compositions, and we do not attribute the terminating signal source. This is an observability/robustness request, not a claimed root-cause report.Environment (pinned)
node_modules/.bin/hyperframes)PUPPETEER_EXECUTABLE_PATH)Observed
Composition: single-document assembly (root + 3 bare-id scene wrappers), 1920x1080@30, 19 s = 570 frames, ~60 KB inline DOM. Forced software raster, two attempts, both exit 1:
frame=265 fps=0.5 ... elapsed=0:08:13inside the error's stderr tail; final errorStreaming encode failed: FFmpeg exited with code 255, tail endsExiting normally, received signal 15.What isolation achieved: a canvas-driven, 570-frame, DOM-light repro under
--no-browser-gpufinishes end-to-end in ≈115 s (≈5 s compile + 45 s unrelated wait + ≈65–70 s capture+encode, ~0.12 s/frame). So the abort appears tied to slow per-frame capture of a DOM-heavy page under software raster, not to frame count alone — and we could not shrink it further into a synthetic repro.Unverified code reading, for triage only (not our diagnosis): in
dist/cli.js(0.6.52),spawnStreamingEncoder(~L32025) attaches two SIGTERM paths to the ffmpeg child: an idle watchdogffmpegStreamingTimeout(default 600 000 ms, envFFMPEG_STREAMING_TIMEOUT_MS) that is reset on writes Node reports as accepted, and the job-levelabortSignalhandler. Both could produce exactly "signal 15"; we cannot tell which one fired from outside the process. For completeness: the capture loop ignoreswriteFrame's boolean return — but in Node that return is Writable backpressure semantics (buffer threshold), not encoder liveness, and we deliberately do not propose treatingfalseas fatal.Asks
error/deadstatus), so the capture loop can stop early instead of spending the remaining capture budget against a terminated encoder — failure at ~19.7 min cost us the full render twice.enableChunkedEncodepath that ships disabled by default)?Package hygiene: redacted log excerpts available on request (consumer wrapper lines removed; scene ids replaced by host-a/b/c; terminal control sequences stripped, verified by scan with 0 remaining control bytes; no consumer-project paths or internal designations).