Skip to content

Add retry logic for transient network failures and failure tracking - #9

Merged
reecemiao merged 1 commit into
mainfrom
claude/rerun-augment-download-errors-cejdbd
Aug 24, 2026
Merged

Add retry logic for transient network failures and failure tracking#9
reecemiao merged 1 commit into
mainfrom
claude/rerun-augment-download-errors-cejdbd

Conversation

@reecemiao

Copy link
Copy Markdown
Owner

This PR adds comprehensive retry handling for transient network failures during YouTube downloads and channel listings, along with persistent failure tracking across runs.

Summary

When downloading videos from YouTube, network interruptions (connection resets, timeouts, etc.) are common and often recoverable with a retry. This change implements automatic retry logic with exponential backoff for transient failures, while distinguishing them from permanent refusals (members-only, deleted videos). Failed videos are now recorded in the state file so they can be retried in later runs, even after they've scrolled out of the normal check window.

Key Changes

Network Failure Detection & Retry Logic

  • Added is_transient() function to distinguish network troubles from permanent refusals by examining exception chains and error messages
  • Implemented _attempt() wrapper in YouTubeClient that retries transient failures with exponential backoff (configurable via retry_backoff)
  • Added YouTubeError.transient flag to mark errors that should be retried
  • Integrated yt-dlp's native retry settings (retries, fragment_retries, extractor_retries, socket_timeout, continuedl)

Failure Tracking & Recovery

  • Extended State class with failures dict to persistently record failed videos with attempt counts, timestamps, and metadata
  • Added record_failure(), failed_videos(), forget_failures(), and attempts() methods to State
  • Modified Pipeline.run() to accept retry_failed and only_failed flags for retrying previously failed videos
  • Failed videos are picked up before the normal listing window, prioritizing oldest failures first

Configuration & CLI

  • Added config options: download_retries, retry_backoff, socket_timeout, retry_failed, retry_max_attempts
  • Added CLI flags: --retry-failed, --no-retry-failed, --only-failed, --retries N
  • New ytscript failures command to list and clear failure records
  • Updated help text and README with retry behavior documentation

Testing

  • Comprehensive test coverage for transient error detection across different exception types and localized error messages
  • Tests for retry behavior with configurable backoff and attempt limits
  • Tests for failure persistence and recovery across runs
  • Tests for CLI integration of retry flags and failure management

Implementation Details

  • Transient errors are detected by checking exception chains (via __cause__ and __context__) for ConnectionError, TimeoutError, specific OSError errno values, and known error message patterns
  • Retry backoff uses exponential growth: retry_backoff * 2^(attempt-1), so with default 5s it waits 5s, 10s, 20s, etc.
  • yt-dlp's .part file resumption means a dropped download at 63% only costs the retry pause, not re-downloading the 63%
  • State file version bumped to 2 to support the new failures field; version 1 files load without failures
  • Failure records include attempt count, timestamps, video metadata, and the error message for debugging

https://claude.ai/code/session_0116BFtHjqyoP9CQoe7mVcym

A backfill of thirty videos rarely gets through without YouTube hanging up
part-way through one of them:

    [download] Got error: ('Connection aborted.', ConnectionResetError(10054,
    '远程主机强迫关闭了一个现有的连接。', None, 10054, None))

That is the network, not the video, and it was costing the whole video: the
run reported a failure and moved on. Two changes, one for each timescale.

During a run, a request that fails on something that looks like network
trouble is made again after retry_backoff seconds, doubling for each of
download_retries extra attempts. yt-dlp resumes from the .part file, so a
drop at 63% costs the pause rather than the 63%. Transient failures are told
apart from refusals by walking the exception chain for connection and timeout
types and their messages, so the check survives a localised OS string; a
members-only or deleted video still fails on the first try, as before.

Between runs, a video that still fails goes into the state file under
"failures" with the error, an attempt count and enough metadata to fetch it
again. This is what a backfill needed: a plain run only looks at the newest
check_limit videos, so a video that failed during the initial thirty was out
of the window by the next run and would never have been seen again.
`run --retry-failed` puts that list back in front of the queue whatever its
age, `run --only-failed` does those and nothing else without listing the
channel, and retry_failed = true makes every run do it. A video that keeps
failing is picked up retry_max_attempts times and then left alone, which
--only-failed overrides and `ytscript failures --clear` resets.

`ytscript failures` shows the list; a video that succeeds drops off it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116BFtHjqyoP9CQoe7mVcym
@reecemiao
reecemiao merged commit 3c2d75d into main Aug 24, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants