watch.skill Error Handling and Failure Modes in Claude Video

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, 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. Each phase runs in a dedicated script with specific failure modes and custom exception types defined in 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.


# 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.


# 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.


# 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.


# 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 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.

When a stage error occurs, 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.


# 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.

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 and other scripts to prevent temporary file accumulation during failures.
  • Standardized output from 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 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. 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 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 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 and catch the specific exception classes imported from 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, which serves as the shared configuration module for the skill. This centralizes error type definitions and allows consistent exception handling across download.py, frames.py, transcribe.py, and whisper.py.

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 →