feat: support only the latest yt-dlp and update it on HTTP 403

A 403 now carries an update-and-retry-once hint, the skill contract tells
the agent how to update yt-dlp through its owning package manager, and CI
installs the latest yt-dlp instead of a pinned baseline.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
bradautomates 2026-09-25 09:34:18 +10:00
parent 3216a9c307
commit fdbffd1c2e
6 changed files with 11 additions and 7 deletions

View File

@ -39,7 +39,7 @@ jobs:
if: runner.os == 'Windows'
run: choco install ffmpeg --no-progress -y
- name: Install test tools
run: python -m pip install pytest "yt-dlp[default]==2026.8.19"
run: python -m pip install --upgrade pytest "yt-dlp[default]"
- name: Verify executables
run: |
ffmpeg -version

View File

@ -15,7 +15,7 @@ All notable changes to `/watch` are documented here.
### Fixed
- SessionStart hook command now quotes `${CLAUDE_PLUGIN_ROOT}`, so plugin paths containing spaces work.
- Uniform sampling now spans the actual capped range and labels selected source frames with their real timestamps, including fractional seeks and cue frames.
- FFmpeg sync-option capability detection, empty keyframe-window fallback, RGB thumbnail deduplication, and actionable probe/launch errors.
- FFmpeg sync-option capability detection (including FFmpeg 9's `-fps_mode[:<stream_spec>]` help format; tested on FFmpeg 4.4 through 9.0), empty keyframe-window fallback, RGB thumbnail deduplication, and actionable probe/launch errors.
- UTF-8 output and subprocess decoding, BOM-aware shared config parsing, provider-key pairing, atomic config updates, keyless readiness, and advisory setup hooks.
- Bounded caption downloads respect watch-owned output templates while retaining intentional yt-dlp configuration. Fresh run directories and completed final-path validation prevent stale/partial media reuse.
- WebVTT optional-hour timestamps, entities, cue/block parsing, and overlap-scoped deduplication preserve independent repeated speech.
@ -23,6 +23,7 @@ All notable changes to `/watch` are documented here.
- Conservative 24,000,000-byte cloud upload budgets, actual chunk/multipart size checks, and explicit partial/no-speech metadata.
### Changed
- Only the latest yt-dlp release is supported. An HTTP 403 now tells the agent to update yt-dlp with its owning package manager and retry once; CI tests against the latest release instead of a pinned one.
- First-success installation documentation separates media tools from optional speech backends and describes host/network limitations.
- User-provided output directories now contain a disposable run child; cleanup preserves the parent and source files.
- The base runtime remains standard-library-only; local model environments and caches live outside the self-contained skill folder.

View File

@ -171,7 +171,7 @@ Tell me which tools are missing and help me install them here.
| Ubuntu/Debian | `sudo apt install python3 ffmpeg pipx`, then `pipx install "yt-dlp[default,curl-cffi]"` and `pipx ensurepath`. Install [Deno](https://docs.deno.com/runtime/getting_started/installation/) for YouTube. |
| Windows | Install [Python 3.10+](https://www.python.org/downloads/windows/), then `winget install --id Gyan.FFmpeg --exact`, `winget install --id yt-dlp.yt-dlp --exact`, and `winget install --id DenoLand.Deno --exact`. |
Reopen the terminal and agent after installation so they can find the new tools. On Windows, verify Python with `python --version` or `py -3 --version`. The tested yt-dlp baseline is **2026.08.19**; Watch uses the executable available in the active environment.
Reopen the terminal and agent after installation so they can find the new tools. On Windows, verify Python with `python --version` or `py -3 --version`. Watch supports the **latest yt-dlp release** only, because sites routinely break older ones; it uses the executable available in the active environment.
## Using a web app?
@ -311,7 +311,7 @@ Update Watch using the same method you installed it with, then start a new sessi
Marketplace auto-update behavior depends on the host and your settings. Updating Watch is separate from updating its media tools.
Update yt-dlp with its owning installer, then verify the same executable with `yt-dlp --version`:
Keep yt-dlp on its latest release. Update it with its owning installer, then verify the same executable with `yt-dlp --version`:
- Homebrew: `brew upgrade yt-dlp`
- pipx: `pipx upgrade yt-dlp`
@ -331,7 +331,7 @@ Ask the agent to run bundled `setup.py --json` for resolved paths/versions, JS-r
| Python opens the Store | Use an installed interpreter verified by `python --version` or `py -3 --version`. |
| FFmpeg option failure | Inspect the actual FFmpeg path; watch probes `-fps_mode` and retains advertised `-vsync` compatibility for older builds. |
| Missing JS runtime/EJS | Update the owning yt-dlp package and follow upstream Deno/EJS setup. |
| 403 / login challenge | Read the original error; use explicit authentication only if you have access. A 403 has no universal workaround. |
| 403 / login challenge | Update yt-dlp to its latest release (see [Updating and troubleshooting](#updating-and-troubleshooting)) and retry once; the agent does this automatically. If the 403 persists, read the original error and use explicit authentication only if you have access. |
| `Gemini auth` / `quota` / `rejected` / `upload` / `service` / `network` / `response` | The Gemini engine failed: bad or missing key, rate limit, a video Google refused (private, unsupported, too long), a failed upload, a Google-side error, no route to `generativelanguage.googleapis.com`, or an unreadable reply. Nothing ran locally; fix the cause or rerun with `--engine local`. |
| 429 | Wait before retrying; the service is rate limiting requests. |
| Explicit hosted egress denial | Check the environment's network settings or use an accessible local source. Cloud ASR/cold model setup still need network access. |

View File

@ -150,7 +150,7 @@ If a small-model transcript is nonsense, suggest the actual spoken-language hint
Cloud fallbacks extract mono 16 kHz MP3 and upload within a conservative 24,000,000-byte file budget. Large files are chunked with source-time offsets restored; missing chunks appear in the final report. Provider errors do not justify automatic provider switching.
For download failures, use the bounded original error and its diagnostic hint. A generic 403 has no universal fix. Do not hardcode alternate clients, cycle cookies, or disable TLS verification. Preserve available captions when media/probing fails. For hosted environments, local uploads solve downloading only; cloud ASR and cold local-model setup still need permitted network access.
For download failures, use the bounded original error and its diagnostic hint. A 403 has no generic fix, but first update yt-dlp with its owning package manager (e.g. `brew upgrade yt-dlp`, `pipx upgrade yt-dlp`) and retry once. Do not hardcode alternate clients, cycle cookies, or disable TLS verification. Preserve available captions when media/probing fails. For hosted environments, local uploads solve downloading only; cloud ASR and cold local-model setup still need permitted network access.
For follow-ups, reuse evidence already viewed before rerunning. Remove only the disposable **Work dir** created by this invocation when no longer needed. Never delete the parent supplied with `--out-dir`, a user source file, the local venv, or model caches as routine cleanup.

View File

@ -54,6 +54,9 @@ def network_diagnostic(stderr: str) -> str:
hint = 'This source requires authentication; supply an explicit cookie option if you have access.'
elif '429' in lower or 'too many requests' in lower:
hint = 'The service is rate limiting requests; wait before trying again.'
elif re.search(r'\b403\b', lower) or 'forbidden' in lower:
# Only the latest yt-dlp is supported; sites routinely break older releases with 403s.
hint = 'No generic fix for a 403; update yt-dlp to its latest release and retry once.'
elif any(s in lower for s in ('egress denied', 'not in the allowlist', 'blocked by network policy')):
hint = 'This environment blocks the destination; check its network settings or use an accessible local file.'
elif any(s in lower for s in ('certificate_verify_failed', 'certificate verify failed', 'unable to get local issuer certificate')):

View File

@ -100,7 +100,7 @@ def test_rejects_outside_final_path(monkeypatch, tmp_path):
@pytest.mark.parametrize('message,hint', [
('HTTP 403 forbidden', 'original error'), ('HTTP 429', 'rate limiting'),
('HTTP Error 403: Forbidden', 'latest release'), ('unexpected extractor failure', 'original error'), ('HTTP 429', 'rate limiting'),
('Sign in to continue', 'authentication'), ('No JavaScript runtime', 'Deno/EJS'),
('egress denied', 'network settings'), ('certificate verify failed', 'trusted CA'),
])