Skip to content

handlePlan never resolves Chromium, breaking probe-stage plans #1055

@kiyeonjeon21

Description

@kiyeonjeon21

Describe the bug

handlePlan in @hyperframes/aws-lambda never sets PRODUCER_HEADLESS_SHELL_PATH, so the moment the producer's probe stage tries to launch Chromium inside Lambda the invocation fails with:

Error: An `executablePath` or `channel` must be specified for `puppeteer-core`
    at assert (/var/task/node_modules/puppeteer-core/src/util/assert.ts:19:11)
    at ChromeLauncher.computeLaunchArguments (/var/task/node_modules/puppeteer-core/src/node/ChromeLauncher.ts:125:7)
    at async ChromeLauncher.launch (/var/task/node_modules/puppeteer-core/src/node/BrowserLauncher.ts:119:24)
    at async launchBrowser (file:///var/task/handler.mjs:376:17)
    at async acquireBrowser (file:///var/task/handler.mjs:349:22)
    at async createCaptureSession (file:///var/task/handler.mjs:12223:36)
    at async runProbeStage (file:///var/task/handler.mjs:36445:20)
    at async plan (file:///var/task/handler.mjs:37797:23)
    at async handlePlan (file:///var/task/handler.mjs:41843:20)

handleRenderChunk resolves Chromium at the top of the function (packages/aws-lambda/src/handler.ts:299-309), but handlePlan skips that step entirely. Anything that makes runProbeStage create a browser session — currently root duration unknown, unresolved nested compositions, or data-hf-auto-start media — can therefore abort before the plan step finishes writing plan.tar.gz.

Note: inline requestAnimationFrame(...) routes capture through screenshot mode later in the pipeline, but it does not by itself force the Plan-stage browser probe unless one of the probe conditions above is also present.

Link to reproduction

Hit this in production while wiring HyperFrames into a customer pipeline; happy to put a stripped-down public repo together if it would help triage. The minimum case is any composition that ends up in runProbeStage with no static duration and no pre-seeded PRODUCER_HEADLESS_SHELL_PATH.

Steps to reproduce

  1. Author a composition where runProbeStage is exercised. The fast way is to omit a static root data-duration so the producer has to probe duration from the browser.
  2. hyperframes lambda deploy (or use an existing stack on @hyperframes/aws-lambda@0.6.40).
  3. hyperframes lambda render <project> --wait.

Expected behavior

Plan resolves Chromium the same way render-chunk does, the probe stage launches headless Chrome, and the plan tar lands in S3.

Actual behavior

The plan Lambda fails with the puppeteer-core "executablePath" assertion. Step Functions retries the Plan task according to the state machine retry policy, then the render is reported as failed.

Root cause

packages/aws-lambda/src/handler.ts:227handlePlan body never touches PRODUCER_HEADLESS_SHELL_PATH. Compare with handleRenderChunk at line 299:

if (!deps?.skipChromeResolution && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
  const chromePath = await resolveChromeExecutablePath();
  process.env.PRODUCER_HEADLESS_SHELL_PATH = chromePath;
}

On a cold container the env var is unset, so plan launches puppeteer with executablePath: undefined. This can only be masked if a warm Lambda execution environment previously handled a renderChunk invocation from another execution and left the env var sticky; within a single Step Functions execution, Plan still runs before RenderChunks.

handleAssemble doesn't launch Chromium so it doesn't need the guard.

Environment

  • @hyperframes/aws-lambda@0.6.40 (also reproduces on 0.6.37–0.6.39)
  • Lambda runtime nodejs22.x, x86_64, Sparticuz Chromium
  • AWS region us-east-1

Notes

I have a candidate patch — same env-var guard copy-pasted into handlePlan, plus a dispatch test that pre-seeds the path and asserts plan doesn't overwrite it. Happy to send it as a PR if the surgical fix is the direction you'd take here. Alternative would be lifting the guard into the dispatcher so any future action gets it for free, but that'd also pre-warm Chromium for assemble which doesn't need it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions