Skip to content

fix(youtube): distinguish transient 429 from a missing transcript#34

Merged
syswave-dev merged 1 commit into
mainfrom
fix/youtube-transcript-429
Jun 22, 2026
Merged

fix(youtube): distinguish transient 429 from a missing transcript#34
syswave-dev merged 1 commit into
mainfrom
fix/youtube-transcript-429

Conversation

@syswave-dev

Copy link
Copy Markdown
Collaborator

Closes #33.

Problem

YouTube videos that have a transcript were rendered as _No transcript available._. The cause is a transient per-IP HTTP 429 on the /api/timedtext content endpoint (anti-bot rate-limit; normal browsing still returns 200, and api.list() still sees the transcript). youtube_transcript_api raises IpBlocked solely on 429, and the sidecar swallowed every exception, so a temporary block was indistinguishable from a genuinely absent transcript — and got cached as the final answer.

Change

  • New markitdown-sidecar/yt_transcript.py (dependency-free): fetch_snippets returns (snippets, status) with status ok | none | blocked | error, classifying a 429 as blocked. _fmt_ts/_format_transcript moved here verbatim.
  • app.py: honest message on blocked ("transcript exists but YouTube rate-limited the request, try again later") instead of _No transcript available._; returns transcript_status.
  • lib/markitdown-client.js: passes transcriptStatus through.
  • lib/web.js: sets result.noStore = true on blocked/error.
  • server.js + lib/mcp.js: skip cache.put when noStore; refresh paths keep the existing good snapshot instead of overwriting it with a placeholder. none/ok cache normally.

A genuinely absent transcript keeps the old message and stays cacheable. Language selection is unchanged (first listed transcript: DE for German videos, EN for English videos).

Tests

  • 761 JS (+5: status passthrough, noStore on blocked/error, no-noStore on ok/none, not-cached at the server layer) — node --test.
  • 9 Python (stdlib-only) — python3 markitdown-sidecar/test_youtube.py.
  • Verified against the real youtube_transcript_api: the reported video now classifies as blocked (previously masked as "none").

🤖 Generated with Claude Code

…oses #33)

YouTube rate-limits the /api/timedtext content endpoint with HTTP 429 +
an anti-bot page (transient, per-IP; normal browsing still returns 200).
youtube_transcript_api raises IpBlocked solely on 429, and the sidecar
swallowed it, so a temporary block was reported as "_No transcript
available._" and cached as the final answer.

Classify the fetch outcome (ok|none|blocked|error) in a new
dependency-free markitdown-sidecar/yt_transcript.py:
- blocked (429): honest "transcript exists but rate-limited, try later"
  message; flagged via transcript_status -> result.noStore so server.js
  and lib/mcp.js skip cache.put and refresh keeps the good snapshot.
- none: genuinely absent -> unchanged message, still cacheable.

Language selection unchanged (first listed transcript). Tests: 761 JS
(+5) and 9 stdlib-only Python (markitdown-sidecar/test_youtube.py).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzC4vKjJBjuAoD1JzyYAWp
@syswave-dev syswave-dev merged commit 46d3714 into main Jun 22, 2026
4 checks passed
@syswave-dev syswave-dev deleted the fix/youtube-transcript-429 branch June 22, 2026 07:58
syswave-dev added a commit that referenced this pull request Jun 22, 2026
PR #34 added markitdown-sidecar/yt_transcript.py (imported by app.py) but
the Dockerfile only COPYied app.py + limits.py, so the deployed image
crash-looped on `ModuleNotFoundError: No module named 'yt_transcript'`.
Add the new module to the COPY layer.


Claude-Session: https://claude.ai/code/session_01QzC4vKjJBjuAoD1JzyYAWp

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
syswave-dev added a commit that referenced this pull request Jun 25, 2026
…metadata on cache hits (#39)

Strip 1x1 tracking pixels in cleanDom, add the sciencedaily-lead-image
recipe (unwrap #text), and serve the full persisted metadata
(og:image/twitter:image, description, author, …) on /api cache hits in
both frontmatter and format=json.

Batches untagged main commits since v3.1.0: #34 (youtube 429 vs missing
transcript), #35 (bundle yt_transcript.py), #37 (X-Transcript-Status).

Bumps version to 3.2.0.
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.

YouTube: "No transcript available" shown even when a transcript exists (transient 429 mislabeled)

1 participant