# How to Troubleshoot yt-dlp Download Failures in Claude Video

> Troubleshoot yt-dlp download failures in Claude Video by checking prerequisites, network restrictions, and script arguments. Fix common errors and ensure smooth video downloads.

- Repository: [bradautomates/claude-video](https://github.com/bradautomates/claude-video)
- Tags: how-to-guide
- Published: 2026-07-26

---

**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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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:

```bash
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:

```bash
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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/download.py) lines 27–44 and run yt-dlp directly in your shell to observe real-time error output:

   ```bash
   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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/watch.py) lines 39–44.

## Practical Code Examples

### Manual Debugging Invocation

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

```bash
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:

```bash
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`](https://github.com/bradautomates/claude-video/blob/main/download.py) constructs the argv list without executing:

```python
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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/download.py) isolates network vs. configuration issues.
- **Fallback options**: If downloads fail permanently, Whisper integration in [`watch.py`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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.