Panniantong/Agent-Reach · error · TranscribeError

{cmd[0]} timed out after {timeout}s

Error message

{cmd[0]} timed out after {timeout}s

What it means

Raised by _run (transcribe.py:155-170) when an external subprocess (yt-dlp, ffmpeg) exceeds its timeout and subprocess.run raises TimeoutExpired. Timeouts are explicit per call site: yt-dlp downloads get 1800s, ffmpeg steps get the 600s default. The guard exists because user-supplied URLs flow into these binaries and a stalled network read must not hang the CLI forever.

Source

Thrown at agent_reach/transcribe.py:170

    return duration


def _run(cmd: List[str], timeout: int = 600) -> None:
    """Run a subprocess, raising TranscribeError on nonzero exit or timeout.

    cmd carries user-supplied URLs/paths into yt-dlp/ffmpeg — a stalled
    network read or a hung probe must not block the CLI forever.
    """
    try:
        proc = subprocess.run(
            cmd,
            capture_output=True,
            encoding="utf-8",
            errors="replace",
            timeout=timeout,
        )
    except subprocess.TimeoutExpired:
        raise TranscribeError(f"{cmd[0]} timed out after {timeout}s")
    if proc.returncode != 0:
        raise TranscribeError(
            f"{cmd[0]} failed (exit {proc.returncode}): {proc.stderr.strip()[:300]}"
        )


def _literal_ip(host: str):
    """Return the address a literal host denotes, or None for a real hostname.

    ``ipaddress`` only accepts the canonical dotted-quad form, but the C
    resolver behind yt-dlp accepts the whole ``inet_aton`` grammar: ``127.1``,
    ``2130706433``, ``0x7f000001`` and ``0177.0.0.1`` all reach 127.0.0.1, and
    ``0xA9FEA9FE`` reaches the cloud metadata endpoint. Parsing with the same
    grammar keeps those shorthands from slipping past the private-address
    check. This is literal parsing only — no name is resolved here.
    """
    try:
        return ipaddress.ip_address(host)

View on GitHub (pinned to 93ae1d18c3)

Solutions

  1. Retry once — transient network stalls are the most common cause
  2. For downloads: pre-download with your own yt-dlp invocation (resumable, -R retries), then pass the local file path to transcribe()
  3. Check the machine's CPU/network: ffmpeg re-encode of a max-size source should finish well under 600s on normal hardware
  4. If consistently hitting the 600s ffmpeg cap, split the source first so each compress/chunk call is smaller

Example fix

// before
transcribe("https://slow-host.example/episode.m4a")  # yt-dlp timed out after 1800s

// after: download out-of-band, then transcribe locally
subprocess.run(["yt-dlp", "-x", "--audio-format", "m4a", "-R", "5", "-o", "ep.m4a", "URL"], check=True)
text = transcribe("ep.m4a")
Defensive patterns

Strategy: retry

Try / catch

from agent_reach.transcribe import TranscribeError
import time
for attempt in range(2):
    try:
        text = transcribe(source)
        break
    except TranscribeError as e:
        if "timed out" in str(e) and attempt == 0:
            time.sleep(5)
            continue
        raise

Prevention

When it happens

Trigger: download_audio() on a very slow / throttled host (yt-dlp killed at 1800s); compress_audio() or chunk_audio() re-encoding a huge or pathological file past 600s. The process is killed by subprocess.run's timeout and TranscribeError wraps it.

Common situations: Slow networks, rate-limited CDN edges, geo-throttled media servers, or an overloaded machine where ffmpeg re-encode of a 512 MiB source takes >10 minutes. Also containerized environments with CPU quotas that make ffmpeg far slower than expected.

Understand the failure class

Related errors


AI-assisted analysis of Panniantong/Agent-Reach@93ae1d18c3 (2026-08-14). Data as JSON: /api/errors/b5e22954efd8174d. Report an issue: GitHub.