How to Troubleshoot yt-dlp Download Failures in Claude Video

When Claude Video's /watch skill fails to download a video, the issue typically stems from missing prerequisites, restrictive network conditions, or incorrect yt-dlp arguments that can be diagnosed by inspecting the download.py script and retrying with explicit flags.

Claude Video (bradautomates/claude-video) relies on yt-dlp to fetch video content and English subtitles through its /watch skill. The download pipeline is implemented in skills/watch/scripts/download.py, where functions like download_url and fetch_captions construct command-line arguments and validate execution. When troubleshooting failures, you must verify that yt-dlp is installed, review the specific error codes returned by the subprocess, and confirm that output files are generated in the expected directory structure.

Verify yt-dlp Installation

The script performs an immediate precondition check using shutil.which("yt-dlp"). In skills/watch/scripts/download.py at lines 20–22 and 66–68, both download_url and fetch_captions invoke SystemExit if the binary is missing from your system PATH.

Run this command to confirm availability:

yt-dlp --version

If the command returns "command not found", install yt-dlp using your platform's package manager:

  • macOS: brew install yt-dlp
  • Linux: apt install yt-dlp or pipx install yt-dlp
  • Windows: winget install yt-dlp

Alternatively, run the setup script provided in the repository:

python3 skills/watch/scripts/setup.py --check

Common Failure Patterns in download.py

Most failures fall into six categories based on the logic in skills/watch/scripts/download.py.

Binary Not Found (SystemExit)

When shutil.which returns None at lines 20–22 or 66–68, the script raises SystemExit with the message indicating yt-dlp is not installed. This prevents any network requests from executing.

Invalid Command-Line Arguments

The argument lists for caption fetching and full downloads are built at lines 71–84 (fetch_captions) and lines 27–44 (download_url). Common configuration errors include invalid output templates or modified subtitle language filters. The test suite in tests/test_download.py specifically enforces that --sub-langs remains limited to en.* patterns; deviating from this constraint causes the harness to fail.

Non-Zero Exit Codes

After executing the subprocess at lines 45–53, download_url inspects result.returncode and verifies file creation. If yt-dlp exits with an error code (common with HTTP 429 rate limits, 403 geo-blocks, or private videos), the script detects the failure but does not suppress the stderr output. You must examine the forwarded stdout/stderr to identify network timeouts or authentication failures.

Subtitle Download Timeouts

The fetch_captions function uses --sub-langs en.* to limit downloads to English tracks. According to the test comment at the top of tests/test_download.py (lines 1–7), requesting all languages (all) instead of en.* causes yt-dlp to hang indefinitely while pulling every available language track.

Missing or Corrupted Video Files

After yt-dlp completes, _pick_video scans the output directory for files matching video.* at lines 55–62. If the video was blocked, the format selection (fmt) was incompatible, or the download was interrupted, this scan returns None and triggers a failure message. The default format string "bv*[height<=720]+ba/b[height<=720]/bv+ba/b" may need adjustment to best for testing problematic URLs.

Permission and Path Errors

At line 23, the script creates the working directory using out_dir.mkdir(parents=True, exist_ok=True). If the parent directory lacks write permissions, yt-dlp cannot write the info JSON or subtitle files, resulting in silent failures or Permission denied errors in stderr.

Step-by-Step Debugging Workflow

Follow this sequence to isolate the root cause:

  1. Validate the binary: Run yt-dlp --version to ensure the executable is in your PATH.

  2. Execute manually: Copy the exact argument list from download.py lines 27–44 and run yt-dlp directly in your shell to observe real-time error output:

    yt-dlp -N 8 -f "bv*[height<=720]+ba/b[height<=720]/bv+ba/b" \
      --merge-output-format mp4 \
      --write-info-json --write-subs --write-auto-subs \
      --sub-langs "en.*" --sub-format vtt --convert-subs vtt \
      --no-playlist --ignore-errors \
      -o "/tmp/watch/video.%(ext)s" \
      "https://www.youtube.com/watch?v=VIDEO_ID"
  3. Inspect output: Check /tmp/watch for video.info.json, *.vtt subtitle files, and the video file itself (video.mp4 or similar).

  4. Check network restrictions: If you encounter HTTP 429 or 403 errors, add --proxy to your command or use --retries 10 and --socket-timeout 30 to handle transient failures.

  5. Verify subtitle flags: Ensure --sub-langs contains only en.* patterns. Regressions here cause the download to stall on videos with extensive multilingual subtitle tracks.

  6. Review Python tracebacks: When invoking watch.py, capture the full stack trace. The script raises descriptive SystemExit exceptions (e.g., "yt-dlp did not produce a video file") that pinpoint whether the failure occurred during download, post-processing, or file validation.

  7. Prepare Whisper fallback: If captions remain unavailable, ensure your API keys are correctly placed in ~/.config/watch/.env so the skill can fall back to Whisper transcription as implemented in watch.py lines 39–44.

Practical Code Examples

Manual Debugging Invocation

Use this complete command to bypass Claude Video and test yt-dlp directly:

yt-dlp \
  -N 8 \
  -f "bv*[height<=720]+ba/b[height<=720]/bv+ba/b" \
  --merge-output-format mp4 \
  --write-info-json \
  --write-subs \
  --write-auto-subs \
  --sub-langs "en.*" \
  --sub-format vtt \
  --convert-subs vtt \
  --no-playlist \
  --ignore-errors \
  -o "/tmp/watch/video.%(ext)s" \
  "https://www.youtube.com/watch?v=rlOpbu3Enkw"

Using the Watch CLI with Debug Output

Run the skill with explicit output directory flags to isolate permissions issues:

watch https://www.youtube.com/watch?v=rlOpbu3Enkw \
  --detail balanced \
  --out-dir /tmp/watch \
  --max-frames 120 \
  --resolution 1024

Inspecting Generated Arguments Programmatically

To verify how download.py constructs the argv list without executing:

from pathlib import Path
import download

captured_calls = []

def mock_run(cmd, *_, **__):
    captured_calls.append(cmd)
    class Result:
        returncode = 0
    return Result()

download.subprocess.run = mock_run
download.download_url("https://example.com/video", Path("/tmp/test"))

print(captured_calls)

# Output shows the exact list passed to yt-dlp

Summary

  • Installation check: skills/watch/scripts/download.py validates yt-dlp presence at lines 20–22 and 66–68 using shutil.which.
  • Argument construction: The download pipeline builds specific yt-dlp flags at lines 27–44 and 71–84, requiring --sub-langs en.* to prevent hangs.
  • Exit code handling: Non-zero return codes and missing files are detected at lines 45–53 and 55–62, with errors forwarded to stderr.
  • Manual testing: Running yt-dlp directly with the flags from download.py isolates network vs. configuration issues.
  • Fallback options: If downloads fail permanently, Whisper integration in watch.py lines 39–44 provides transcription alternative when API keys are configured in ~/.config/watch/.env.

Frequently Asked Questions

Why does the download hang when fetching subtitles?

The fetch_captions function in skills/watch/scripts/download.py uses --sub-langs "en.*" to restrict downloads to English tracks. If this filter is modified to all, yt-dlp attempts to download every available language, causing indefinite stalls as noted in tests/test_download.py lines 1–7. Always verify the subtitle language argument matches en.* patterns.

How do I fix "yt-dlp is not installed" errors when I know it's on my system?

Claude Video checks for the binary using shutil.which("yt-dlp") at lines 20–22 of download.py. If your shell PATH differs from the Python environment's PATH, the check fails. Ensure yt-dlp is installed in a globally accessible location like /usr/local/bin or /opt/homebrew/bin, or explicitly add its location to your system PATH before launching the watch skill.

What does "yt-dlp did not produce a video file" mean?

After the subprocess completes, _pick_video at lines 55–62 scans the output directory for files matching video.*. This error indicates yt-dlp exited successfully but failed to write the expected file, often due to geo-restrictions, private videos, or format selection mismatches. Check that the URL is accessible from your network and consider changing the format parameter from the default 720p filter to best in download_url at line 26.

Can I use a proxy to bypass rate limits or geo-blocks?

Yes. While download.py does not expose proxy flags directly, you can test connectivity by running yt-dlp manually with --proxy http://proxy:port added to the standard argument list. Once confirmed working, you can temporarily modify the cmd list construction in download_url (lines 27–44) to include your proxy settings, or set the HTTP_PROXY environment variable before running the watch command.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →