Skip to content

Tolerate per-URL operational errors instead of aborting the run - #23

Merged
jbothma merged 1 commit into
mainfrom
tolerate-operational-errors
Aug 4, 2026
Merged

Tolerate per-URL operational errors instead of aborting the run#23
jbothma merged 1 commit into
mainfrom
tolerate-operational-errors

Conversation

@jbothma

@jbothma jbothma commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What

Make a run tolerate per-URL failures: gather and count them, summarise at the end, and complete regardless.

Why

A kolkhoz-manual job aborted mid-run when one URL's capture raised:

File ".../pravda/pravda.py", line 113, in _finalize_capture
    async with asyncio.timeout(HAR_PROCESSING_TIMEOUT_S):
TimeoutError

Pravda deliberately does not persist this kind of failure — its own code notes that inner HAR-processing / storage timeouts are operational errors that it raises, as opposed to HTTP/browser failures which it stores on the snapshot with error set. The raised error propagated out of asyncio.gather(*(snap(url) …)) in capture_urls and took down the entire run, discarding all the work that had already succeeded.

Change

  • capture_urls now catches exceptions per URL, logs a warning, records an OperationalError(stage, url, error), and returns (captures, errors). A URL that fails operationally is simply absent from the captures mapping (Pravda-persisted errored snapshots still appear there, unchanged).
  • The extraction loop is wrapped the same way, so one page's extraction failing (LLM/parse/IO) doesn't abort the run either.
  • summarise_errors logs, at the end of the run, a total plus counts by stage and by exception type, then the per-URL detail.
  • The pipeline now writes outputs and completes even when some URLs failed.

except Exception is deliberate breadth for "let the run finish"; CancelledError / KeyboardInterrupt are BaseException and still propagate.

Operational errors go to the log only, not the database — the database stays reserved for the capture failures Pravda persists on the snapshot.

Testing

  • ruff clean.
  • Behavioural test: with 4 URLs where 2 raise TimeoutError, capture_urls returns the 2 good captures and 2 recorded errors, and summarise_errors logs the by-stage/by-type/per-URL summary without raising.
  • Not yet run end-to-end on the cluster.

🤖 Generated with Claude Code

A single capture raising an operational error (e.g. Pravda's HAR
processing timeout, which Pravda raises rather than persists) propagated
out of asyncio.gather and aborted the entire run.

Gather these per-URL failures instead: capture_urls now catches
exceptions per URL, records them as OperationalError, and returns them
alongside the successful captures; the extraction loop does the same.
summarise_errors logs a count (by stage and exception type) plus the
per-URL detail at the end of the run. The run now completes and writes
outputs in spite of these errors.

These are operational errors, so they go to the log only — not the
database, which is reserved for the capture failures Pravda persists on
the snapshot (HTTP status, browser failures).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jbothma
jbothma merged commit 68eaffc into main Aug 4, 2026
1 check passed
@jbothma
jbothma deleted the tolerate-operational-errors branch August 4, 2026 15:45
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