YouTube timedtext caption-fetch returns HTTP 429 (per-IP rate limit) under volume. Currently tapir silently maps 429 → SourceNone — identical to "genuinely no transcript". Users see skipped videos with no explanation and the video is never retried.
With multiple users this compounds: N users × M subscriptions × rapid scrapes = IP throttled immediately, making auto-summarization-as-default unsafe until this is solved.
Slices
T1 — Distinguish 429 from SourceNone in the adapter + store transcript status
FetchTranscript returns SourceNone on any non-200. Add domain.SourceRateLimited (or typed error) so the engine can distinguish "no captions" from "blocked, retry later".
Migration: videos.transcript_status (pending | rate_limited | fetched | none) so the runner can skip already-fetched and re-queue rate-limited across restarts.
T2 — Runner backoff: persist + respect rate-limit state across runs
On SourceRateLimited: record state in DB, skip on next run until a backoff window expires (TAPIR_FETCH_BACKOFF, e.g. 1h default).
Exponential + jitter backoff on repeated 429s for the same video.
TAPIR_FETCH_DELAY stays for per-video pacing; this adds per-video retry-after logic on top.
T3 — UI signal: show rate-limited state visibly
List shows rate-limited videos with a distinct "Retrying later" badge — not the Summarize button, not blank.
Once T1+T2 land and the throttle is honest: flip auto_summarize default from false → true (one-line migration change, gated on this issue).
Acceptance criteria
A 429 on captions is never silently treated as SourceNone.
Repeated runs correctly back off and eventually retry rate-limited videos.
Users see a visible "retrying later" state, not a silent skip.
After this lands: auto-summarization default can safely be flipped to true.
## Problem
YouTube timedtext caption-fetch returns HTTP 429 (per-IP rate limit) under volume. Currently tapir silently maps 429 → SourceNone — identical to "genuinely no transcript". Users see skipped videos with no explanation and the video is never retried.
With multiple users this compounds: N users × M subscriptions × rapid scrapes = IP throttled immediately, making auto-summarization-as-default unsafe until this is solved.
## Slices
**T1 — Distinguish 429 from SourceNone in the adapter + store transcript status**
- `FetchTranscript` returns `SourceNone` on any non-200. Add `domain.SourceRateLimited` (or typed error) so the engine can distinguish "no captions" from "blocked, retry later".
- Migration: `videos.transcript_status` (`pending | rate_limited | fetched | none`) so the runner can skip already-fetched and re-queue rate-limited across restarts.
**T2 — Runner backoff: persist + respect rate-limit state across runs**
- On `SourceRateLimited`: record state in DB, skip on next run until a backoff window expires (`TAPIR_FETCH_BACKOFF`, e.g. 1h default).
- Exponential + jitter backoff on repeated 429s for the same video.
- `TAPIR_FETCH_DELAY` stays for per-video pacing; this adds per-video retry-after logic on top.
**T3 — UI signal: show rate-limited state visibly**
- List shows rate-limited videos with a distinct "Retrying later" badge — not the Summarize button, not blank.
- Once T1+T2 land and the throttle is honest: flip `auto_summarize` default from `false` → `true` (one-line migration change, gated on this issue).
## Acceptance criteria
- A 429 on captions is never silently treated as SourceNone.
- Repeated runs correctly back off and eventually retry rate-limited videos.
- Users see a visible "retrying later" state, not a silent skip.
- After this lands: auto-summarization default can safely be flipped to true.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
YouTube timedtext caption-fetch returns HTTP 429 (per-IP rate limit) under volume. Currently tapir silently maps 429 → SourceNone — identical to "genuinely no transcript". Users see skipped videos with no explanation and the video is never retried.
With multiple users this compounds: N users × M subscriptions × rapid scrapes = IP throttled immediately, making auto-summarization-as-default unsafe until this is solved.
Slices
T1 — Distinguish 429 from SourceNone in the adapter + store transcript status
FetchTranscriptreturnsSourceNoneon any non-200. Adddomain.SourceRateLimited(or typed error) so the engine can distinguish "no captions" from "blocked, retry later".videos.transcript_status(pending | rate_limited | fetched | none) so the runner can skip already-fetched and re-queue rate-limited across restarts.T2 — Runner backoff: persist + respect rate-limit state across runs
SourceRateLimited: record state in DB, skip on next run until a backoff window expires (TAPIR_FETCH_BACKOFF, e.g. 1h default).TAPIR_FETCH_DELAYstays for per-video pacing; this adds per-video retry-after logic on top.T3 — UI signal: show rate-limited state visibly
auto_summarizedefault fromfalse→true(one-line migration change, gated on this issue).Acceptance criteria