Repository navigation
1.5.17: Fail the canary probe on an empty track instead of transcribing it - #69
Merged
Merged
Conversation
With speech-to-text on, an empty caption track on the canary video got a speech-to-text answer. The canary counted that answer as a working caption path: it set transcriptor_canary_ok to 1 and could end a failure streak while captions failed. The probe now passes skipWhisper, so an empty track is a failed probe and no transcription runs. A speech-to-text answer that came after WHISPER_TIMEOUT also went into the cache for a call with skipCache. The late write now checks skipCache like the other cache writes. ADR 003 now says that the probe never falls back to speech-to-text. Fixes #59. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review of #59 found two sentences that claim too much. A failed probe still fills the track-list, info and chapters entries: the "no subtitles" answer reads the track list, and a miss runs one metadata run. ADR 003 now says only that the probe does not read a cached transcript and does not store one. The CHANGELOG entry now says that the late speech-to-text answer went under the official English track of the canary video. Only calls that asked for that track by name read it: auto-discovery skips a speech-to-text answer stored under a track name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the entries of this PR under 1.5.17 and bump the version in package.json, package-lock.json and server.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
samson-art
marked this pull request as ready for review
September 30, 2026 12:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #59
What and why
The canary is the periodic test request of the HTTP server. It finds out whether captions still come back from the platform. The probe in
src/canary.tscallsvalidateAndDownloadSubtitlesfor one video withtype: "official",lang: "en"andskipCache. The review of #50 found two faults in this path. Both are older than #50.WHISPER_MODEislocalorapi), an empty track on the canary video started the speech-to-text fallback. When the fallback answered, the call resolved withsource: 'whisper'. The canary counted that answer as a working caption path. It settranscriptor_canary_okto 1 while captions failed, and it ended an open failure streak.handleExplicitRequestFlow(src/validation.ts), a speech-to-text job that ran longer thanWHISPER_TIMEOUTwrote its answer to the cache later. That late write ignoredskipCache. So a late answer to the probe went into the cache as the official English track of the canary video. Calls that asked for that track by name then got speech-to-text text instead of captions until the entry expired (CACHE_TTL_SUBTITLES_SECONDS, 7 days by default).Two tests showed both faults before the fix (see Verified).
The fix:
validateAndDownloadSubtitlesgets a second option,skipWhisper.handleExplicitRequestFlowstarts speech-to-text only whenskipWhisperis not set andWHISPER_MODEis notoff. The same value goes towhisperTried, so the "no subtitles" text does not say that speech-to-text ran. The canary passes{ skipCache: true, skipWhisper: true }. An empty track is now a failed probe: the gauge goes to 0 and the streak goes on.skipCacheis set. This is one guard in the shared function. Real calls do not setskipCache, so they keep the late write.Only the canary passes these options. The 5 other callers of
validateAndDownloadSubtitles(2 insrc/index.ts, 3 insrc/mcp-core.ts) pass none, and both options are off by default. The tool contract, the caller texts of real calls, metric names and labels, and the per-call log line do not change. No caption request is added. With speech-to-text on, a failed probe no longer downloads audio or runs a transcription.Commits: 2dcf21d (the fix and the tests), e19a890 (review 1, text only).
Decisions
The maintainer took these decisions on 2026-09-26, in the brief:
handleExplicitRequestFlow: the late speech-to-text write afterWHISPER_TIMEOUTobeysskipCache.source: 'whisper'counts as a failure" to "a probe whose track is empty never starts speech-to-text, counts as a failure, sets the gauge to 0 and keeps the streak going".fix/1.5.17-canary-no-whisper. The entries go under## [Unreleased].Acceptance criteria
src/validation.test.ts:does not start speech-to-text for a probe whose track is empty. It setsWHISPER_MODEtolocal, gives an empty track and a speech-to-text job that answers, and passes the options of the canary. It expectsNotFoundError, no call tostartOrReuseWhisperJob, and an error text that does not name speech-to-text.src/canary.test.ts:counts an empty track as a failure when speech-to-text is on. Two ticks givetranscriptor_canary_ok0 and onecanary: transcript path failingwith reasonnot_found.src/canary.test.ts:probes CANARY_URL with one explicit language and skips the cache and speech-to-text.skipCachecall whose speech-to-text job finishes afterWHISPER_TIMEOUTwrites nothing to the cache. Test insrc/validation.test.ts:does not write a late speech-to-text answer to the cache when skipCache is set. The existing testshould call cache.set when Whisper finishes after WHISPER_TIMEOUT (explicit lang)still passes, so real calls keep the late write.Plan
Files that change
src/validation.ts: the options ofvalidateAndDownloadSubtitlesgetskipWhisper?: boolean. A doc comment says that the options apply only to a request that nameslang.handleExplicitRequestFlowtakes{ skipCache, skipWhisper }instead ofskipCachealone. The valuetranscribe(skipWhispernot set andWHISPER_MODEnotoff) decides the fallback andwhisperTried. The late write returns early whenskipCacheis set.src/canary.ts: the probe passesskipWhisper: true. The header comment says that an empty track is a failed probe.src/canary.test.ts: one test renamed and changed to expectskipWhisper, and one new test.src/validation.test.ts: two new tests.docs/adr/003-caption-request-budget.md: the Decision says that the probe never falls back to speech-to-text, that an empty track is a failed probe, and that the probe does not read a cached transcript and does not store one. The stale sentence "When Whisper answers the probe, the run includes the whole transcription" is removed. A new Do-not rule says not to let speech-to-text answer the probe. The Date and Sources lines name The canary counts a speech-to-text answer as a working caption path, and its late result fills the cache #59.CHANGELOG.md: two entries under[Unreleased], Fixed.No env var changes, so
.env.exampleand the README do not change.Order of work
red-59.txt): 2 tests failed in each suite.skipWhisper, thetranscribevalue and theskipCacheguard. PassedskipWhisperfrom the canary. Updated ADR 003 and the CHANGELOG. Commit 2dcf21d.avail,infoandchaptersentries. Second, the CHANGELOG said that real calls for the canary video got speech-to-text text. Only calls that named the official English track read it, because auto-discovery skips a speech-to-text answer stored under a track name (testdoes not take a speech-to-text answer stored under a track name for that track). Fixed in e19a890: text only, no code change.Risks
subtitles_extraction_failures_total{reason="no_subtitles"}. That counter counts a failure only when speech-to-text ran (1.5.13). The CHANGELOG says so. Names and labels do not change.throwNoSubtitlesError. On a cache miss, this is one metadata run. The probe made this run before with speech-to-text off. With speech-to-text on, the failed probe now makes this run instead of an audio download.Verified
Red before the fix (an excerpt of
red-59.txt, on 152a609 with the new tests):Green after the fix, on 2dcf21d:
npx tsc --noEmit -p .: no errors.npx jest src/canary.test.ts -w 2: 16 of 16.npx jest src/validation.test.ts -w 2: 120 of 120.npx eslinton the 4 changed.tsfiles: no problems.On e19a890:
npx jest src/canary.test.ts src/validation.test.ts -w 2with a name filter for the acceptance-criteria tests: 7 passed.make check-no-smoke(format-check, lint, typecheck, full Jest, build) passed in the pre-commit hook on 2dcf21d and on e19a890. On 2dcf21d it ran twice: on the first commit, and on an amend that restored the ADR 003 italics after a prettier run changed them. The test count of the gate was not kept.Mutation drill on 2dcf21d: 4 of 4 mutations fail a named test. The file was restored after each mutation.
src/canary.ts: removeskipWhisper: truefrom the probe call.probes CANARY_URL with one explicit language and skips the cache and speech-to-textandcounts an empty track as a failure when speech-to-text is on(2 of 16 incanary.test.ts).src/validation.ts:const transcribe = whisperConfig.mode !== 'off'(ignoreskipWhisper).does not start speech-to-text for a probe whose track is empty(1 of 120 invalidation.test.ts).src/validation.ts:whisperTried: whisperConfig.mode !== 'off'instead oftranscribe.does not start speech-to-text for a probe whose track is empty, because the error text says that speech-to-text was tried (1 of 120).src/validation.ts: the late write guard goes back toif (!text?.trim())(noskipCache).does not write a late speech-to-text answer to the cache when skipCache is set(1 of 120).After review 1, mutations 1, 2 and 4 ran again on e19a890 and failed the same tests (3 of 3). The review fix changes only text, so it has no mutation of its own.
Not verified
make check, smoke, e2e and load tests: not run. They call real YouTube from this IP.downloadSubtitles,startOrReuseWhisperJob,getWhisperConfigand the cacheset.Not in scope
throwNoSubtitlesErroron a failed probe, and theavail,infoandchaptersentries that it fills. It was there before with speech-to-text off, and this PR does not add it. To remove it, pass the track list tothrowNoSubtitlesError, or skip the list whenskipCacheis set. The plan puts it out of scope, and the review did not ask for it.skipCacheandskipWhisperfor a request withoutlang.handleAutoDiscoverFlowignores them, as it did before. The canary always nameslang, and the doc comment on the options says so.After merge
After the deploy, on the MCP HTTP server. Only the HTTP server runs the canary.
transcriptor_canary_okis 1 and thattranscriptor_canary_last_success_timestamp_secondsmoves forward. The canary video has an official English track, so on a normal day the probe gets a track and nothing changes.WHISPER_MODEtolocalorapi. If it does not, neither fault happens there, and only step 1 applies.canary: transcript fetch failedorcanary: transcript path failingwith reasonnot_found,whisper_requests_totaldoes not grow at the time of that probe. The gauge stays at 0 until a track comes back.CACHE_MODE=redis: read the entrysub:<canary URL>:official:en:<format>. The canary URL isCANARY_URL, or the default insrc/canary.ts. The format isYT_DLP_SUB_FORMAT, orsrtby default. If its JSON has"source":"whisper", a late write from an old probe can have stored it. Delete that one key, or wait until its TTL ends.🤖 Generated with Claude Code