fix(cli): transcribe --model large-v3 crashes on unknown DTW preset#2086
Conversation
… large-v3) `hyperframes transcribe --model large-v3` aborted with "unknown DTW preset 'large-v3'". whisper.cpp's --dtw flag wants a dotted alignment-heads preset (large.v3), but we passed the hyphenated ggml file stem (large-v3). They coincide for tiny/base/small/medium(+.en) — why it slipped through — but diverge for the large-v* family. Map stem -> preset (- to .) so large-v1/v2/v3 (and large-v3-turbo) work; no-op for the others. Also fixes media-use, which shells to `hyperframes transcribe`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv
vanceingalls
left a comment
There was a problem hiding this comment.
R1 — hyperframes #2086 at f691e081
🟢 LGTM. Targeted fix for the X-reported large-v3 transcription crash (@IChooseAI's Chinese-translated report: "HyperFrame Transcribe throws an error when using the large-v3 model"). Root-cause analysis reads clean — whisper.cpp's --dtw alignment-heads presets are dotted (large.v3, large.v3.turbo) while ggml model file stems are hyphenated (large-v3, large-v3-turbo), and they coincide on tiny/base/small/medium(+.en) which is exactly why the divergence stayed hidden until a large-v* user hit it.
Verified
- Mapping is correct for the reported failure surface.
dtwPresetForModel("large-v3") === "large.v3",dtwPresetForModel("large-v3-turbo") === "large.v3.turbo". Both are documented whisper.cpp preset names. - Preset-identical models untouched. No-op for
tiny,base.en,small,small.en,medium.en— tests cover. - Namespace kept separate at argv layer.
--modelstill gets the file stem (effectiveModel); only--dtwgets the transform.transcribe.ts:299swap is minimal. - Downstream reach. PR body's claim that this also fixes media-use's whisper fallback (which shells to
hyperframes transcribe) is correct — media-use inherits the fix without a media-use PR. - Test shape. 4 hyphenated large-family cases + 5 preset-identical (tiny/base.en/small.en/medium.en/small) via
test.each. Regression can't reintroduce without a test change.
Observation (non-blocking)
O1 — Global - → . replace is broader than the target namespace. For a model with a hyphen that ISN'T in whisper.cpp's DTW preset table (a fine-tuned my-domain-model, a hypothetical future large-v4 before whisper.cpp lists large.v4, or a quantized variant like large-v3-q5_0 → large.v3.q5_0), the mapping runs and produces a preset whisper-cli still doesn't recognize. Result: same crash, different error text ("unknown DTW preset ''" vs "unknown DTW preset ''"). Doesn't regress the reported case; doesn't cover the adjacent unknown-preset case either.
A whitelist-with-fallback shape (Map<model, preset> for known families, skip --dtw entirely when unknown) would trade the two lines for graceful degradation on custom / future / quantized models — silent no-DTW-alignment instead of whisper-cli crash. Not a merge-blocker; the dtwPresetForModel name already advertises the whitelist API, so this is a swap for later. Flagging now so a next-model crash-report doesn't come as a surprise.
CI
In-flight at review time: Build, Test (the load-bearing one — runs the new vitest cases), Typecheck, CodeQL js/ts, Windows render, Preview parity, CLI smoke (win/required). Green already: Format, Lint, Fallow, CLI shim ubuntu+macOS, SDK unit/contract/smoke, Runtime contract, Studio load smoke, Player perf. My 🟢 assumes Test clears.
R1 by Via
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at f691e08.
Nice narrow crash-fix — the diagnosis (hyphenated model stem vs dotted DTW preset) is spot-on, and dtwPresetForModel is minimal in the best way. Traced through transcribe.ts; the split between --model <path> and --dtw <preset> is preserved, .en → multilingual switchover at L230-238 still passes the stem through dtwPresetForModel, and the test asserts both the fix and the "unchanged for preset-identical families" invariant (not just a "should throw" enshrining the bug — good).
Concerns
-
Class-of-crashes coverage is partial (
packages/cli/src/whisper/transcribe.ts:216). The tweeted crash + all documented models (tiny.en, base.en, small.en, medium.en, large-v3) are covered, pluslarge-v1/v2andlarge-v3-turbo. But whisper.cpp's known DTW presets end there — the model-download machinery inmanager.ts:17doesn't validate against a supported set, it just fetchesggml-${model}.bin, so quantized variants that Hugging Face publishes are technically reachable.--model large-v3-q5_0→dtwPresetForModel→large.v3.q5_0→ whisper-cli aborts with the same "unknown DTW preset" class-of-error. Same formedium-q5_0,small.en-tdrz, etc. Not blocking (these aren't advertised), but worth naming as a follow-up: either validate--modelagainst a known set at the CLI layer, or makedtwPresetForModela real dispatch that falls back to a safe preset for quantized/variant families. (per feedback_bandaid_vs_longterm_rubric — narrow crash-fix vs class fix, and feedback_dispatch_map_silent_drop_default_types — currently the.replace()is a silent-map that produces syntactically-plausible-but-invalid presets.) -
PR-body claim I couldn't verify: "media-use transcription — its whisper fallback shells to
hyperframes transcribe". Greppedskills/media-use/— the SKILL.md andscripts/don't shell tohyperframes transcribeanywhere I could find. The onehyperframes transcribereference I did find inskills/hyperframes-media/scripts/heygen-tts.mjsis a printed hint, not a subprocess. If the "media-use also fixed" claim refers to a caller in another repo (e.g. an internal skill using the CLI), fine — but if it's aboutskills/media-use/in this repo, I don't see the shell-out. Non-blocking; just noting for your own claim-verification.
Nits
-
Doc drift (
packages/cli/src/commands/transcribe.ts:38,docs/packages/cli.mdx).--modelhelp still lists "Options: tiny.en, base.en, small.en, medium.en, large-v3". This fix newly makeslarge-v3-turbo/large-v2/large-v1actually work — worth surfacing at leastlarge-v3-turboin the help text so users know it's a supported option now (previously it crashed the same way, so keeping it hidden made sense; now it doesn't). One-line update in the help + mdx. -
Observability on failure-path (
packages/cli/src/commands/transcribe.ts:171-179). The catch just printsTranscription failed: ${message}and exits 1. No telemetry event captures which model / which error class — so the next "unknown DTW preset" (or any similar class-of-crashes) recurrence surfaces via Twitter, not via your own signals. Pre-existing, not this PR's job to fix, but the current bug was the exact shape a telemetry gap creates. Follow-up ticket, not a blocker here.
What I didn't verify
- I didn't run
vitestlocally; taking the PR body's "9/9" at face value. - I didn't confirm whisper.cpp's current preset list against upstream source — I'm going on the PR body's characterization (
large.v1,large.v2,large.v3,large.v3.turbo) matching the error message. If whisper.cpp added a new preset since, the tests would still be right but the.replacemay map further exotic variants correctly or not.
Small, well-scoped fix for a real customer-facing bug — LGTM from my side, leaving as a comment.
— Review by Rames D Jusso
Problem
hyperframes transcribe audio.mp3 --model large-v3(andlarge-v2/large-v1) fails with:Root cause
packages/cli/src/whisper/transcribe.tspassed the ggml model file-stem straight to whisper.cpp's--dtwflag. But those are two different namespaces:ggml-large-v3.bin--dtwalignment-heads presets are dotted:large.v3,large.v3.turboThey coincide for
tiny/base/small/medium(+.en) — the models most people use — which is why it went unnoticed. They diverge for thelarge-v*family, so--dtw large-v3hits whisper-cli's unknown-preset guard and aborts.Fix
A small exported
dtwPresetForModel(model)that maps the file-stem to the preset (-→.): a no-op for the tiny/base/small/medium families, correct forlarge-v1/v2/v3andlarge-v3-turbo. Used only for the--dtwarg;--modelstill gets the file path.Also fixes media-use transcription — its whisper fallback shells to
hyperframes transcribe, so it inherited the same crash for large models.Test
packages/cli/src/whisper/transcribe.test.ts— table-driven cases for the large family (hyphen→dot) and the preset-identical families (unchanged).vitest run9/9.Verified: cli
tsc --noEmitexit 0, oxlint/oxfmt clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv