Skip to content

fix(media-use): report a failed transcription as an anomaly - #5819

Open
kevin9327 wants to merge 1 commit into
heygen-com:mainfrom
kevin9327:fix/media-use-transcription-anomaly
Open

kevin9327 wants to merge 1 commit into
heygen-com:mainfrom
kevin9327:fix/media-use-transcription-anomaly

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

Refs #5564 (part 1 — silent empty words; part 2, the bare "fetch failed", is #5568)

Problem

For non-HeyGen engines (Gemini, ElevenLabs, …) audio.mjs gets word timings from transcribeWav(), which returns null when npx hyperframes transcribe fails (e.g. no whisper-cpp / Parakeet installed) or produces no words. The script then wrote words: [] to audio_meta.json and finished with no anomaly, so downstream caption/sync steps silently had nothing to work with and the user had no hint why.

Fix

When transcription returns nothing, push a non-fatal anomaly for that line (with the npx hyperframes transcribe <file> command to see the underlying error). Behaviour is otherwise unchanged: the voice is still written with words: [] and the run still exits 0.

anomalies (non-fatal):
  - line intro: no word timings — transcription failed or found no words (run `npx hyperframes transcribe audio/voice/intro.wav` to see why)

skills-manifest.json regenerated with gen-skills-manifest.ts (only the media-use hash changes).

Test

New case in skills/media-use/audio/scripts/gemini-pipeline.test.mjs: Gemini engine with a fake npx that prints "Captions skipped — whisper-cpp not found" and exits 1; asserts exit 0, words: [], and the anomaly in stdout.

  • before the fix: node --test skills/media-use/audio/scripts/gemini-pipeline.test.mjs → 4 pass, 1 fail (no anomaly reported)
  • after: 5 pass
  • node --test skills/media-use/audio/scripts/*.test.mjs skills/media-use/audio/scripts/lib/*.test.mjs → 158/158 pass
  • oxfmt --check / oxlint clean on touched files; gen-skills-manifest.ts --check in sync

This change was prepared with AI assistance and reviewed/tested by me.

@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.

Thanks for this. The diagnosis is right and the test is the right shape: it spawns the real script with a fake npx that exits the way a missing ASR engine does, so it enters through the production path rather than a lifted helper. I checked transcribeWav and it does only ever return null or a non-empty array, so the null test is the correct trigger for the engines you named, and the run still exits 0 with words left empty, as described.

The fix stops one line short of the root, though. In lib/tts.mjs the HeyGen branch returns words: [] whenever the response has no word_timestamps, or when every entry is dropped by the sentinel and finite-number filters. In audio.mjs the new anomaly sits inside if (!wordArr), and an empty array is truthy, so the HeyGen path skips both the transcription fallback and the anomaly. A HeyGen run that comes back with audio and no timings still writes an empty words list and finishes silently, which is the same symptom on what is usually the default provider.

Keeping the fallback trigger on null and testing emptiness for the anomaly covers both in one place, something like if (!wordArr?.length) after the fallback. A second test case with a HeyGen response that has no word_timestamps would lock it in.

Separately, the branch needs a rebase before it can merge. skills-manifest.json conflicts: your change moves the media-use entry to a new hash at 113 files, and main is now at a different hash with 116 files after another change landed under that skill. The manifest should be regenerated after the rebase rather than hand-merged, otherwise the in-sync check will fail on the merge commit. CI is otherwise genuinely green at this head, 58 runs with nothing failing.

One tiny thing: the anomaly is pushed before the duration check, so a line with no timings and a bad duration reports both, including the one saying the voice was omitted.

@jrusso1020 jrusso1020 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.

The fix is worth landing, but the branch needs a rebase and there is still one gap. Nothing has been pushed since Miguel's change request, so both of his points stand.

Rebase. skills-manifest.json conflicts: main's media-use entry is now hash 1082bd1c4e3f033b with 116 files. Rebase and regenerate with gen-skills-manifest.ts rather than resolving the hash by hand.

The HeyGen path is still silent. I checked main: the HeyGen branch of lib/tts.mjs (around line 343) returns words: [] when the response has no word_timestamps, or when every entry is filtered out. An empty array is truthy, so in audio.mjs it skips both the transcription fallback and the new anomaly. Keeping the fallback on null and raising the anomaly on !wordArr?.length after it covers both providers in one place. A second test with a HeyGen response that has no word_timestamps would pin it.

The Gemini test itself is good: it drives the real script with a fake npx that fails the way a missing ASR engine does.

Need: yes, missing word timings are silent today · Size: ok · Reuse: uses the existing anomalies channel · Simpler: one emptiness check after the fallback covers both providers

— Rames

wordArr = await transcribeWav({ wavRel: rel, lang, hyperframesDir });
// null: `hyperframes transcribe` failed (e.g. no whisper-cpp/Parakeet) or found no words.
if (!wordArr)
anomalies.push(

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.

This only fires when transcribeWav returns null. The HeyGen path returns words: [] (truthy) when the response has no usable word_timestamps, so it never reaches here. Moving the check after the fallback as if (!wordArr?.length) would report both cases.

@kevin9327

Copy link
Copy Markdown
Contributor Author

Thanks both. Pushed:

  • The anomaly now fires on !wordArr?.length after the fallback, so the HeyGen path (words: [] with no usable word_timestamps) is reported too. The fallback still triggers on null only.
  • It is pushed after the duration check, so a line with a bad duration no longer also reports missing timings.
  • New test: a HeyGen response with no word_timestamps and a failing npx hyperframes transcribe; it fails on the previous audio.mjs and passes now.
  • Merged origin/main into the branch (instead of rewriting history) and regenerated skills-manifest.json with gen-skills-manifest.ts: only the media-use hash changes, 1082bd1c4e3f033b → 99c9ace317939450.

node --test over the audio scripts passes.

AI assistance: this change was prepared with AI assistance and verified by running the tests above.

@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.

Thanks for the quick turnaround. Everything from the earlier round is addressed at this head:

  • The anomaly now fires on an empty result after the fallback, so the HeyGen path that returns words: [] is reported too, and the fallback still only runs on null.
  • It is pushed after the duration check, so a line with a bad duration reports only the omission.
  • The manifest was regenerated after merging main; only the media-use hash changes and the in-sync check passes.

I ran the pipeline tests at this head (6 pass) and then swapped in main's audio.mjs: both new tests fail and the four existing ones still pass, so they guard the change. Both drive the real script, which is the right way in.

Two small things, neither blocking:

  • On the HeyGen path the transcription fallback never runs (an empty array skips it), but the message still says "transcription failed ... run npx hyperframes transcribe to see why". Wording that covers both causes, for example "no word timings (the provider returned none, or transcription failed)", would avoid sending HeyGen users to a command that probably works. The fake npx in the HeyGen test is never called for the same reason, so its comment could say so.
  • All four commits on the branch are unsigned, and main requires signed commits, so they need re-signing before this can be queued.

Verdict: APPROVE
Reasoning: Both earlier findings are fixed at this head, the two new tests fail without the change and pass with it, and CI is green; only a wording nit and commit signing remain.

auto-merge was automatically disabled October 25, 2026 12:17

Head branch was pushed to by a user without write access

@kevin9327
kevin9327 force-pushed the fix/media-use-transcription-anomaly branch from 90f9069 to 14dce51 Compare October 25, 2026 12:17
@kevin9327

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews. I squashed the branch into one signed commit (14dce516, shows as Verified) on top of current main. The code is the same as the approved head, no changes beyond the rebase. skills-manifest.json is regenerated against current main (that was the conflict).

@jrusso1020

Copy link
Copy Markdown
Collaborator

Thanks for re-signing. It conflicts with main again, so could you rebase and push once more? We'll re-approve at the new head.

@kevin9327 kevin9327 closed this Oct 26, 2026
@kevin9327
kevin9327 force-pushed the fix/media-use-transcription-anomaly branch from 14dce51 to 64c7bbf Compare October 26, 2026 00:14
@kevin9327

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (64c7bbf2) as one signed commit, bf51001f (Verified). The code is unchanged from the approved head; skills-manifest.json is regenerated with gen-skills-manifest.ts and --check passes. gemini-pipeline.test.mjs passes.

@kevin9327 kevin9327 reopened this Oct 26, 2026

@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-checked at bf51001. I ran gemini-pipeline.test.mjs: 6 passed. With audio.mjs put back to the merge base, 2 fail, so the tests guard the change. Together with #5568 this covers #5564.

Verdict: APPROVE
Reasoning: reports a failed or empty transcription where the pipeline already reports anomalies; tests fail without it; CI green.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 26, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 26, 2026
auto-merge was automatically disabled October 28, 2026 00:25

Head branch was pushed to by a user without write access

@kevin9327
kevin9327 force-pushed the fix/media-use-transcription-anomaly branch from bf51001 to 11227f6 Compare October 28, 2026 00:25
@kevin9327

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and pushed a signed commit. I regenerated the conflicting generated file (skills-manifest.json / position-edits-render-inline.ts) with the repo's generator. The PR's tests pass locally at the new head.

@kevin9327
kevin9327 force-pushed the fix/media-use-transcription-anomaly branch 2 times, most recently from 3b44274 to 65a7ce6 Compare October 30, 2026 00:16
@kevin9327

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (f7db6ba4) as one signed commit, 65a7ce62. The code is unchanged; skills-manifest.json is regenerated with gen-skills-manifest.ts and --check passes. gemini-pipeline.test.mjs passes (6 tests).

@jrusso1020 jrusso1020 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.

Reviewed at 65a7ce627685f12f7fdb3b3a7319f80e2bd31a8b.

The empty-word check is now after the fallback and covers both a null transcription and HeyGen returning an empty timings array. This fixes the earlier review finding without changing successful audio output.

At this exact head, node --test skills/media-use/audio/scripts/gemini-pipeline.test.mjs passed all 6 tests with controlled network and media executables. No paid transcription was sent.

Need: yes · Size: ok · Reuse: existing implementation and tests · Simpler: already bounded · Structure: fine
Risk: low - bounded change; validation limits are stated above.

Verdict: APPROVE

Reasoning: The empty-word check is now after the fallback and covers both a null transcription and HeyGen returning an empty timings array. This fixes the earlier review finding without changing successful audio output.

@jrusso1020
jrusso1020 added this pull request to the merge queue Oct 30, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 30, 2026
@kevin9327
kevin9327 force-pushed the fix/media-use-transcription-anomaly branch from 65a7ce6 to cd71165 Compare October 30, 2026 21:29
@kevin9327
kevin9327 force-pushed the fix/media-use-transcription-anomaly branch from cd71165 to 5c94226 Compare October 30, 2026 21:30
@kevin9327

Copy link
Copy Markdown
Contributor Author

Rebased onto main (4cee7be) to clear the conflict in skills-manifest.json; the only change from the approved 65a7ce6 is the regenerated media-use hash. gemini-pipeline.test.mjs still passes (6/6).

@github-actions

Copy link
Copy Markdown
Contributor

This PR has had no activity for 14 days, so it is marked stale and will close in 7 days. Push a commit or leave a comment to keep it open. A maintainer can add keep-open to exempt it.

@github-actions github-actions Bot added the stale label Nov 14, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants