fix: Crossref 429 resilience + salvage sweep + OpenAlex select fix (v1.3.1)
Problem
A parallel batch of reference checks against Crossref's anonymous pool reliably hits 429 storms: 10 concurrent query.bibliographic searches get rate-limited, the fixed 300ms/1200ms retry backoff re-enters the same window, all attempts die, and the majority of a real bibliography lands as check_failed ("could not be checked"). Reproduced with a realistic 35-ref APA list: 14/35 check_failed, 58× HTTP 429.
Separately, every OpenAlex /works lookup silently failed with HTTP 400 because is_oa is not a valid top-level select field — no citation ever got citation-count / OA enrichment.
Fix
fetchRetry: honorRetry-After(delta-seconds or HTTP-date, capped 30s) on 429 and share the wait across all requests to the same host via a per-host cooldown map, with jitter so a released batch does not stampede back in lockstep. 5xx/network/timeout keep the existing backoff.- Salvage sweep (
quickCheck+checkDocument): refs that still endedcheck_failedare re-checked once, sequentially, after the burst has drained, under a 60s wall-clock budget. Transient failures self-heal instead of surfacing to the user. - OpenAlex: select
open_access(valid) and readopen_access.is_oa.
Result (same 35-ref list, anonymous pool)
Before: 14 check_failed / 15 verified, OpenAlex 100% HTTP 400.
After: 0 check_failed / 25 verified / 9 partial / 1 not_found, OpenAlex enrichment working. 429s still occur but are absorbed.
Tests
10 new tests (http-retry, salvage, openalex select); full suite 138 pass.
Summary by cubic
Improves Crossref rate-limit handling and restores OpenAlex enrichment, reducing false check_failed results during parallel checks. Honors server backoff and retries failed refs once to self-heal transient errors.
- Bug Fixes
- 429 resilience:
fetchRetryhonorsRetry-After(seconds or HTTP-date), applies a shared per-host cooldown with jitter, and keeps existing backoff for 5xx/network errors. - Salvage sweep: after parallel checks, re-run
check_failedrefs once, sequentially, under a 60s budget inquickCheckandcheckDocument. - OpenAlex: select
open_accessand readopen_access.is_oa(replaces invalidis_oaselect) to restore citation-count and OA enrichment.
- 429 resilience:
Summary by CodeRabbit
- Bug Fixes
- Improved handling of rate limits by honoring server-provided retry timing and coordinating retries for requests to the same host.
- Added a follow-up check for references affected by temporary network failures.
- Fixed OpenAlex enrichment errors by using the correct open-access response field.
- Tests
- Added coverage for rate-limit retries, OpenAlex lookups, and recovery from temporary citation-check failures.