Skip to content

feat: transient-error retry policy with failure telemetry - #8

Merged
Hydaspex merged 3 commits into
mainfrom
feat/retry-policy
Aug 8, 2026
Merged

feat: transient-error retry policy with failure telemetry#8
Hydaspex merged 3 commits into
mainfrom
feat/retry-policy

Conversation

@Hydaspex

@Hydaspex Hydaspex commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Closes the last gap from the drift/retries review: previously there were no retries anywhere, and per-page crawl failures vanished silently. Follows on from #7.

What's included

ports.pyRetryPolicy (frozen, validated)

  • attempts >= 1, backoff_seconds >= 0; linear backoff (backoff * attempt) — deliberate politeness towards a public NHS site, where exponential retries are the wrong posture
  • Docstring encodes the rule: validation failures are never retried — only transient transport errors and soft (unsuccessful-result) failures

backends/crawl4ai_backend.py — retry engine

  • _arun_with_retries: retries arun on raised exceptions (transport/browser errors) and unsuccessful results (soft failures, e.g. timeouts)
  • Exhaustion semantics: soft failures → CrawlError with the site's error message; transport errors → the original exception propagates
  • No policy = single attempt — previous behaviour preserved exactly
  • sleeper injection hook so tests record backoff delays without sleeping

Failure telemetry

  • backend.last_failed_pages — URLs of unsuccessful page results, recorded per crawl and reset between calls; pages are still skipped (partial results kept) but no longer silently
  • PipelineResult.failed_pagesrun_pipeline aggregates telemetry via duck-typed getattr(backend, "last_failed_pages", ()), so backends without the attribute are unaffected
  • CLI prints a failed-pages warning after the summary

CLI--attempts (default 3) / --backoff (default 2.0) wired through build_backend.

Tests (17 new + 2 adjusted) — policy validation; 6 scrape-retry scenarios (transient-then-success, soft-fail-then-success, exhaustion both modes, single-attempt default, recorded backoff [2.0, 4.0]); crawl telemetry + reset; pipeline aggregation with partial results kept; no-telemetry backend; CLI flags. The two test_preflight.py monkeypatch lambdas were widened to *a, **kw for the new build_backend signature.

Verification

8 scenarios executed in the sandbox before pushing — transient retry, soft-fail retry, exhaustion both modes, single-attempt default, telemetry, linear backoff, validation — all green.

Design decisions to review

  1. Linear over exponential backoff — politeness posture for a public NHS site; exponential is available later via a strategy field if ever needed.
  2. Soft failures retried, validation failures never — a success=False from the crawler is usually transient; a ValueError from our own validation is deterministic.
  3. Duck-typed telemetry (getattr convention) over a port change — keeps the protocol minimal; a formal TelemetryBackend protocol is a possible later refinement.

- RetryPolicy (attempts >= 1, backoff >= 0, linear backoff) in ports.py
- Crawl4AIBackend retries arun on raised exceptions AND unsuccessful
  results; exhaustion raises CrawlError (soft failures) or the original
  error (exceptions); no policy preserves single-attempt behaviour
- Per-page failures in crawl results are recorded on
  backend.last_failed_pages instead of vanishing; run_pipeline
  aggregates them into PipelineResult.failed_pages via duck-typed
  getattr, so other backends are unaffected
- CLI: --attempts/--backoff wired through build_backend
- 8 retry scenarios executed in the sandbox (transient retry, soft-fail
  retry, exhaustion both modes, single-attempt default, telemetry,
  linear backoff [2.0, 4.0], validation)
- run.py: drop unused dataclasses.field import
- test_retry_policy.py: drop unused CrawlOptions import and unused
  page assignment in the soft-fail retry test
The factory built a fresh FlakyCrawler with the full script per call,
so the second crawl replayed the first script entry. One shared
crawler advances the script as intended.
@Hydaspex
Hydaspex merged commit c99ccb7 into main Aug 8, 2026
3 checks passed
@Hydaspex
Hydaspex deleted the feat/retry-policy branch August 8, 2026 17:05
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.

1 participant