# watch.skill Error Handling and Failure Modes in Claude Video

> Explore watch.skill error handling and failure modes in Claude Video. Learn how specific exceptions like DownloadError and TranscribeError are caught and reported for robust video processing.

- Repository: [bradautomates/claude-video](https://github.com/bradautomates/claude-video)
- Tags: deep-dive
- Published: 2026-08-06

---

**The `watch.skill` module implements a four-stage pipeline that catches specific exceptions—`DownloadError`, `FramesError`, `TranscribeError`, and `WhisperAPIError`—and propagates them to the orchestrator in [`watch.py`](https://github.com/bradautomates/claude-video/blob/main/watch.py), which surfaces user-friendly messages prefixed with "⚠️ watch‑skill error:" before exiting with status code 1.**

The `watch.skill` in the **bradautomates/claude-video** repository processes video URLs and local files through a sequential pipeline of download, frame extraction, and transcription stages. Each stage implements defensive error handling to manage network failures, missing dependencies, and API errors, ensuring the skill terminates gracefully with clear diagnostic output when operations fail.

## Pipeline Stages and Failure Points

The skill executes four sequential phases orchestrated by [`skills/watch/scripts/watch.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/watch.py). Each phase runs in a dedicated script with specific failure modes and custom exception types defined in [`skills/watch/scripts/config.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/config.py).

### Download Failures in download.py

The **download stage** invokes `yt‑dlp` via `subprocess.run()` to fetch remote video content. Failure modes include network timeouts, DNS resolution errors, HTTP 404 responses, and non-zero exit codes from the downloader.

When `yt‑dlp` fails, the script catches `subprocess.CalledProcessError` and raises a custom `DownloadError` that includes the exit code and stderr output. This exception propagates upward to abort the pipeline before subsequent stages execute.

```python

# Pattern found in skills/watch/scripts/download.py

try:
    subprocess.run(cmd, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as exc:
    raise DownloadError(
        f"Download failed (exit {exc.returncode}): {exc.stderr.strip()}"
    ) from exc

```

### Frame Extraction Errors in frames.py

The **frame extraction stage** depends on `ffmpeg` and `ffprobe` binaries to generate image frames from the downloaded video. Failure modes include missing or incompatible `ffmpeg` installations, unsupported video codecs, corrupt container formats, and frame rate computation errors.

The script executes `ffmpeg` with `check=True` to ensure any non-zero exit status raises `subprocess.CalledProcessError`. This is immediately wrapped in a `FramesError` and re-raised to halt processing when the video format is invalid.

```python

# Error handling pattern in skills/watch/scripts/frames.py

try:
    subprocess.run(ffmpeg_cmd, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as exc:
    raise FramesError(
        f"Frame extraction failed: {exc.stderr.strip()}"
    ) from exc

```

### Local Transcription Failures in transcribe.py

The **local transcription stage** uses OpenAI Whisper to convert audio to text. Failure modes include `ffmpeg` failures during WAV conversion, missing model weights, and insufficient system RAM causing model load failures.

Errors from the Whisper library are caught and wrapped in `TranscribeError`. The script guarantees cleanup of temporary audio files by placing deletion logic in a `finally` block, preventing orphaned data even when transcription crashes.

```python

# Pattern from skills/watch/scripts/transcribe.py

try:
    result = model.transcribe(audio_path)
except Exception as exc:
    raise TranscribeError(f"Transcription failed: {exc}") from exc
finally:
    if os.path.exists(audio_path):
        os.remove(audio_path)

```

### Remote API Errors in whisper.py

The **remote Whisper API client** handles HTTP communication with external transcription services. Failure modes include missing API keys (read from `~/.config/watch/.env`), authentication errors (HTTP 401), rate limiting (HTTP 429), server errors (HTTP 5xx), and network timeouts.

The script validates HTTP response codes explicitly, raising `WhisperAPIError` with status code and response body details to aid debugging.

```python

# Error handling in skills/watch/scripts/whisper.py

resp = requests.post(url, headers=headers, json=payload, timeout=30)
if resp.status_code != 200:
    raise WhisperAPIError(
        f"Whisper API returned {resp.status_code}: {resp.text}"
    )

```

## Orchestration and Error Propagation in watch.py

The [`skills/watch/scripts/watch.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/watch.py) file serves as the pipeline orchestrator through its `run_watch()` function. It surrounds each stage execution with `try/except` blocks that catch the specific exception types defined in [`config.py`](https://github.com/bradautomates/claude-video/blob/main/config.py).

When a stage error occurs, [`watch.py`](https://github.com/bradautomates/claude-video/blob/main/watch.py) prints a standardized error message prefixed with **"⚠️ watch‑skill error:"** followed by the exception message, then exits with status code 1. For unexpected exceptions outside the defined error types, a generic fallback prints the traceback (visible in development mode) and exits with the same non-zero status.

```python

# Conceptual pattern from skills/watch/scripts/watch.py

def run_watch(video_source, question=None):
    try:
        download_video(video_source)
        extract_frames()
        transcribe_audio()
    except DownloadError as e:
        print(f"⚠️ watch‑skill error: {e}")
        sys.exit(1)
    except FramesError as e:
        print(f"⚠️ watch‑skill error: {e}")
        sys.exit(1)
    # Additional exception handlers...

```

## Programmatic Error Handling

When embedding the skill in Python applications, import the `run_watch` function and catch the custom exceptions to implement custom retry logic or fallback workflows.

```python
from skills.watch.scripts.watch import run_watch
from skills.watch.scripts.config import DownloadError, WhisperAPIError

try:
    result = run_watch("https://example.com/video.mp4", "Summarize this")
except DownloadError as e:
    print(f"Network failure: {e}")
except WhisperAPIError as e:
    print(f"API quota exceeded: {e}")
except Exception as e:
    print(f"Unexpected failure: {e}")

```

## Summary

- **Specific exception types** (`DownloadError`, `FramesError`, `TranscribeError`, `WhisperAPIError`) isolate failure modes to specific pipeline stages in bradautomates/claude-video.
- **Subprocess error wrapping** converts `CalledProcessError` from `yt‑dlp` and `ffmpeg` into actionable error messages containing exit codes and stderr output.
- **Resource cleanup** occurs in `finally` blocks within [`transcribe.py`](https://github.com/bradautomates/claude-video/blob/main/transcribe.py) and other scripts to prevent temporary file accumulation during failures.
- **Standardized output** from [`watch.py`](https://github.com/bradautomates/claude-video/blob/main/watch.py) prefixes all errors with "⚠️ watch‑skill error:" and exits with status code 1, ensuring host systems receive clear failure signals.
- **Configuration errors** for the remote Whisper API are detected early by validating the presence of API keys in `~/.config/watch/.env`.

## Frequently Asked Questions

### What happens when a download fails in watch.skill?

The [`download.py`](https://github.com/bradautomates/claude-video/blob/main/download.py) script catches `subprocess.CalledProcessError` from `yt‑dlp`, wraps it in a `DownloadError` with the specific exit code and stderr message, and propagates this to [`watch.py`](https://github.com/bradautomates/claude-video/blob/main/watch.py). The orchestrator prints "⚠️ watch‑skill error:" followed by the failure details and exits with status code 1, preventing the pipeline from attempting frame extraction on missing data.

### How does watch.skill handle missing ffmpeg dependencies?

During the frame extraction stage, [`frames.py`](https://github.com/bradautomates/claude-video/blob/main/frames.py) executes `ffmpeg` with `check=True`, which raises `subprocess.CalledProcessError` if the binary is missing or returns a non-zero exit code. This is immediately wrapped in a `FramesError` and propagated upward, causing [`watch.py`](https://github.com/bradautomates/claude-video/blob/main/watch.py) to terminate with a descriptive message about the dependency failure.

### Can I customize error handling when embedding watch.skill programmatically?

Yes. Import `run_watch` from [`skills/watch/scripts/watch.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/watch.py) and catch the specific exception classes imported from [`skills/watch/scripts/config.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/config.py). This allows you to implement retry logic for transient `DownloadError` or fallback behavior when `WhisperAPIError` indicates rate limiting, while receiving the full error context from the original failure.

### Where are the custom exception types like DownloadError defined?

According to the source code structure, custom exception classes are defined in [`skills/watch/scripts/config.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/config.py), which serves as the shared configuration module for the skill. This centralizes error type definitions and allows consistent exception handling across [`download.py`](https://github.com/bradautomates/claude-video/blob/main/download.py), [`frames.py`](https://github.com/bradautomates/claude-video/blob/main/frames.py), [`transcribe.py`](https://github.com/bradautomates/claude-video/blob/main/transcribe.py), and [`whisper.py`](https://github.com/bradautomates/claude-video/blob/main/whisper.py).