Skip to content

fix(producer): attach src URL to ffprobe failures for compile-phase attribution (STUDIO-5433) - #3033

Merged
vanceingalls merged 2 commits into
mainfrom
via/studio-5433-ffprobe-src-url
Aug 21, 2026
Merged

fix(producer): attach src URL to ffprobe failures for compile-phase attribution (STUDIO-5433)#3033
vanceingalls merged 2 commits into
mainfrom
via/studio-5433-ffprobe-src-url

Conversation

@vanceingalls

Copy link
Copy Markdown
Collaborator

What

Wrap the video-branch extractMediaMetadata and probeMediaProfile calls in resolveMediaDuration (packages/producer/src/services/htmlCompiler.ts) with a withSrcContext helper that re-throws with the remote src appended as [src=<url>]. The URL is passed through redactTelemetryString first so pre-signed URL signatures never reach telemetry.

Why

STUDIO-5433 — enterprise customer mdave@manh.com was blocked from generating AI Studio videos, surfacing in Datadog as [FFmpeg] ffprobe exit with code 1: [mov,mp4,m4a,3gp,3g2,mj2 @ 0x...] moov atom not found\n[input]: Invalid data found when processing input. runFfprobe at engine/utils/ffprobe.ts:74-79 intentionally redacts the local filePath from the error (see redactFfprobeInput — same file, lines 13-35), so the failure carries no attribution and identifying the offending source requires dumping the Temporal activity history for the workflow.

Root-cause investigation confirms the failing file is MOV/MP4-family (per the demuxer signature) and comes from astral-agent's composition-HTML preview pipeline (path prefix astral-agent/output/hf-preview-*), but the specific <video src> URL wasn't recoverable from indexed Datadog logs — the signed URL had expired 3h before investigation, and neither the producer worker nor the streaming activity logs any per-src URL context around the probe call.

This change surfaces the URL so the next occurrence is diagnosable directly from the render error in Datadog, without a Temporal history dump. That unblocks writer-side hardening as a follow-up.

Preserves fail-fast semantics: the video branch still throws (aborts the compile), unlike the audio branch's deliberate graceful-degrade to duration=0. Only the error message is enriched; the control flow is unchanged.

How

  1. packages/producer/src/services/htmlCompiler.ts

    • New withSrcContext(error) helper inside resolveMediaDuration that wraps error.message with [src=<redactTelemetryString(src)>] and preserves the original stack.
    • Video-branch probeMediaProfile catch re-throws via withSrcContext (was: bare throw error).
    • Video-branch extractMediaMetadata newly wrapped in try/catch that re-throws via withSrcContext (was: uncaught, so the caller saw the bare [input]-redacted ffprobe message).
    • Adds redactTelemetryString import from @hyperframes/core (already exported at packages/core/src/index.ts:255).
  2. packages/producer/src/services/htmlCompiler.test.ts

    • New describe("STUDIO-5433 — ffprobe failure includes src URL for attribution") block with a compileForRender integration test: writes a 0-byte assets/clip.mp4, references it from a <video src> tag, asserts the thrown error message contains [src=assets/clip.mp4] AND still carries the original ffprobe diagnostic so downstream failure classifiers continue to match.

Test plan

  • Local repro: 0-byte mp4 → compileForRender → error message contains [src=assets/clip.mp4] (integration test above).
  • Preserves fail-fast semantics — video branch still throws (assertion on thrown error type + message content).
  • Query-string signatures are stripped via the existing redactTelemetryString (packages/core/src/telemetryRedaction.ts:8redactUrlQueryStrings) — no pre-signed URL secrets leak into Datadog.
  • Focused CI must pass; hosted CI to follow.
  • Follow-up (separate PR pending URL recovery): identify the astral-agent writer that produces the actual failing derivative and add _probe_section_integrity fail-closed at the write site (the durable fix — this PR is diagnosability defense-in-depth).

Enterprise release / feature flag holdout

  • This change is not behind a feature flag (small diagnostic improvement on an existing error path; preserves failure semantics unchanged).
  • This change is behind a feature flag

UX/Screenshot recording

  • No UI impact — enriches a producer-worker error message read only in Datadog.

— Via

founderqiang pushed a commit to founderqiang/hyperframes that referenced this pull request Aug 7, 2026
…tion

STUDIO-5433 defense: when the downloaded media file begins with
<!DOCTYPE, <html, or <?xml, throw a typed HtmlNotVideoError naming
the offending src instead of letting ffprobe emit an inscrutable
moov-atom-not-found on a plain HTML page.

Complements heygen-com#3033 diagnosability layer. Root-cause EF fix ships
separately.

Signed-off-by: Via <vance@heygen.com>
…ttribution (STUDIO-5433)

Wrap the video-branch `extractMediaMetadata` and `probeMediaProfile` calls in
`resolveMediaDuration` (`packages/producer/src/services/htmlCompiler.ts`) with a
`withSrcContext` helper that re-throws with the remote `src` appended as
`[src=<url>]`. The URL is passed through `redactTelemetryString` first so
pre-signed URL signatures never reach telemetry.

STUDIO-5433 — enterprise customer `mdave@manh.com` was blocked from generating
AI Studio videos, surfacing in Datadog as `[FFmpeg] ffprobe exit with code 1:
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x...] moov atom not found\n[input]: Invalid data
found when processing input`. `runFfprobe` at `engine/utils/ffprobe.ts:74-79`
intentionally redacts the local `filePath` from the error (see
`redactFfprobeInput` — same file, lines 13-35), so the failure carries no
attribution and identifying the offending source requires dumping the Temporal
activity history for the workflow.

That dump is expensive-per-occurrence and blocks debugging on operator
availability. The demuxer signature (`mov,mp4,m4a,3gp,3g2,mj2`) tells us the
file is MOV/MP4-family, and the workflow_id tells us which HyperFrames
composition element failed — but the *actual URL* that ffprobe was handed is
lost. This change surfaces the URL so the next occurrence is diagnosable
directly from the render error in Datadog, without a Temporal history dump.

Preserves fail-fast semantics: the video branch still throws (aborts the
compile), unlike the audio branch's deliberate graceful-degrade to
`duration=0`. Only the error *message* is enriched; the control flow is
unchanged.

1. `packages/producer/src/services/htmlCompiler.ts`
   - New `withSrcContext(error)` helper inside `resolveMediaDuration` that
     wraps `error.message` with `[src=<redactTelemetryString(src)>]` and
     preserves the original stack.
   - Video-branch `probeMediaProfile` catch re-throws via `withSrcContext`
     (was: bare `throw error`).
   - Video-branch `extractMediaMetadata` newly wrapped in try/catch that
     re-throws via `withSrcContext` (was: uncaught, so the caller saw the
     bare `[input]`-redacted ffprobe message).
   - Adds `redactTelemetryString` import from `@hyperframes/core` (already
     re-exported at `packages/core/src/index.ts:255`).
2. `packages/producer/src/services/htmlCompiler.test.ts`
   - New `describe("STUDIO-5433 — ffprobe failure includes src URL for
     attribution")` block with a `compileForRender` integration test:
     writes a 0-byte `assets/clip.mp4`, references it from an `<video src>`
     tag, asserts the thrown error message contains `[src=assets/clip.mp4]`
     AND still carries the original ffprobe diagnostic so downstream
     failure classifiers continue to match.

- [x] Repro locally: 0-byte mp4 → `compileForRender` → error message contains
      `[src=assets/clip.mp4]` (test above).
- [x] Preserves fail-fast semantics — video branch still throws (assertion on
      thrown error).
- [ ] Focused CI must pass; hosted CI to follow.
- [ ] Follow-up (separate PR pending URL recovery): identify the writer that
      produces the actual failing derivative and add `_probe_section_integrity`
      fail-closed at the write site (the durable fix — this PR is
      diagnosability defense-in-depth).

<!-- pr-check:enterprise-ff:start -->
- [x] This change is not behind a feature flag (small diagnostic improvement
      on an existing error path; preserves failure semantics unchanged).
- [ ] This change is behind a feature flag
<!-- pr-check:enterprise-ff:end -->

<!-- pr-check:ui-impact -->
- [x] <!-- pr-opt:no-ui-impact --> No UI impact — enriches a producer-worker
      error message read only in Datadog.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vanceingalls
vanceingalls force-pushed the via/studio-5433-ffprobe-src-url branch from 19601bc to 50ca61f Compare August 21, 2026 04:45

@miga-heygen miga-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve. Correct src attribution on ffprobe failures (STUDIO-5433). redactTelemetryString strips pre-signed URL signatures before they reach telemetry — security-correct. Original stack trace preserved, fail-fast semantics unchanged for the video branch, audio graceful-degrade path untouched. Test uses a real 0-byte mp4 to trigger the exact ffprobe failure class. Verified both the video-metadata catch and the probe catch re-throw with src context.

— Miga

@terencecho terencecho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested — producer unit tests are RED on head with two real issues.

1. Blocking regression: video-branch withSrcContext destroys the NotMediaPayloadError type discriminant.

htmlCompiler.ts:493 catches NotMediaPayloadError from assertMediaPayload and re-throws via withSrcContext, which wraps it into new Error(msg + [src=…]). That loses .code = "NOT_MEDIA_PAYLOAD", .owner = "user", .retryable = false, and .elementFingerprints. Downstream SAFE_RENDER_ERROR_CODES + the distributed retry set both key on those fields — they're the exact routing metadata the audio-branch payload-sniff (on main) was added to install for STUDIO-5433 in the first place. Net effect for a <video> src pointing at an HTML/JSON payload (the STUDIO-5433 root case): routing flips from NOT_MEDIA_PAYLOAD / user / no-retry → generic / system / retryable, which pages ops and re-runs the render on a user-input bug.

Pinned by the existing test compileForRender non-media payload sniff > aborts with NotMediaPayloadError before ffprobe when a <video> src is an HTML payload — that test used to pass on main and now fails on head with Expected constructor: [class NotMediaPayloadError extends Error]. That's the regression signal.

Fix: skip wrapping when the error is already a typed routing error, e.g.

const withSrcContext = (error: unknown): Error => {
  if (error instanceof NotMediaPayloadError) return error; // already correlated + routed
  const originalMessage = error instanceof Error ? error.message : String(error);
  ...
};

2. The PR's own new test fails because redactTelemetryString redacts relative-path srcs to [path].

STUDIO-5433 — ffprobe failure includes src URL for attribution > wraps the ffprobe error with [src=<relative-path>] asserts message.toContain("[src=assets/clip.mp4]"), but assets/clip.mp4 matches the BARE_RELATIVE_PATH regex in packages/core/src/telemetryRedaction.ts:96-104 (one separator + .mp4 extension) and is replaced with [path]. Actual message contains [src=[path]], so the assertion fails.

Verified the production case still works: for an https URL (https://foo.com/…/bar.mp4?sig=…), redactTelemetryString correctly strips only the query and preserves host+path — the URL-attribution benefit for STUDIO-5433's remote-URL scenario is real. The test just doesn't reflect it. Either switch the fixture to a remote URL (mock the downloader) so the assertion still means something, or assert [src=[path]] and add a separate case that pins the URL-preservation contract for the actual failing shape.

CI: Producer: unit tests FAILURE covers both above. Test is the transitive-required job that mirrors it. Hosted regression shards + Windows tests still in progress. Head SHA 50ca61fe.

— Review by tai (pr-review)

`withSrcContext` rebuilt every error as a bare `new Error(...)`, which
dropped `NotMediaPayloadError`'s `.code = "NOT_MEDIA_PAYLOAD"`, `.owner =
"user"`, `.retryable = false` and `.elementFingerprints`. `SAFE_RENDER_ERROR_CODES`
and the distributed retry set both key on those, so a `<video>` src pointing
at an HTML payload — the STUDIO-5433 root case — flipped from
NOT_MEDIA_PAYLOAD/user/no-retry to generic/system/retryable: it paged ops and
re-ran the render on a user-input bug. The existing sniff regression
("aborts with NotMediaPayloadError before ffprobe…") is the pin; it fails on
the removal of this one line.

The PR's own new test also asserted `[src=assets/clip.mp4]`, but a bare
relative path matches `telemetryRedaction`'s BARE_RELATIVE_PATH shape and
redacts to `[path]`. Assert what the redactor actually produces for a local
src, and pin the case the ticket is about — a remote URL, where host and path
survive and only the pre-signed query is dropped — directly on the redactor.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed exact head bd53ac66. NotMediaPayloadError now passes through the src-context helper unchanged, preserving code/owner/retryable/fingerprints, while generic ffprobe errors still gain redacted attribution. The relative-path expectation now matches [path], and the added remote-URL assertion pins host/path preservation with query redaction. Current required checks are green.

Verdict: APPROVE
Reasoning: typed routing semantics and both attribution/redaction contracts are now covered without weakening the original diagnostic improvement.

— Magi

@terencecho terencecho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delta-conversion of my prior CHANGES_REQUESTED at head 50ca61f. Verified at new head bd53ac6: NotMediaPayloadError now passes through withSrcContext intact (typed guard preserves .code / .owner / .retryable / .elementFingerprints routing signals); relative and remote URL redaction contracts both pinned by new tests. Concur with @miguel-heygen. — Review by tai (pr-review)

@vanceingalls
vanceingalls merged commit 00f08e8 into main Aug 21, 2026
60 checks passed
@vanceingalls
vanceingalls deleted the via/studio-5433-ffprobe-src-url branch August 21, 2026 06:39
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.

4 participants