Skip to content

fix: pace concurrent NCBI research requests - #239

Merged
Advait Paliwal (advaitpaliwal) merged 4 commits into
mainfrom
codex/ncbi-rate-limit-237
Aug 21, 2026
Merged

fix: pace concurrent NCBI research requests#239
Advait Paliwal (advaitpaliwal) merged 4 commits into
mainfrom
codex/ncbi-rate-limit-237

Conversation

@advaitpaliwal

Copy link
Copy Markdown
Contributor

Summary

  • pace every NCBI E-utilities caller through one process-wide request-start queue
  • forward NCBI_API_KEY to E-utilities while redacting it from provenance
  • keep PubMed timeouts armed through response-body reads
  • document the keyed and shared-IP pacing settings

This ports the three exact code commits from #238 onto a maintainer-owned branch, then adds the missing public configuration documentation. It preserves contributor authorship on the code commits.

Closes #237.
Supersedes #238 after this branch passes maintainer CI.

Verified

  • official NCBI policy: 3 requests/sec without a key, 10 requests/sec with a key
  • live anonymous 12-search burst on main: 0/12 succeeded, 12 rate-limited
  • live anonymous 12-search burst on this branch: 12/12 succeeded, 0 rate-limited
  • focused NCBI regressions: 10/10
  • full npm test: 803/803
  • npm run typecheck
  • npm run build
  • npm run architecture:check
  • root and website production audits: 0 vulnerabilities
  • website lint, typecheck, and build: 34 pages
  • git diff --check

NCBI allows 3 E-utilities requests/sec/IP, 10 with an API key. Pi runs
sibling tool calls from one assistant message concurrently, so a single
research turn fires a dozen PubMed requests at once and most come back
429. Nothing limited how fast they went out.

Add a small in-process gate that spaces request starts, and forward
NCBI_API_KEY, which ncbiIdentityParams() never sent even though
scrubEndpoint() below it already redacted an api_key param.

Measured live, twelve concurrent searches through the registered tool:
0/12 on main, 12/12 with spacing, both anonymously and with a key.

Retry is deliberately not part of this. NCBI sends no Retry-After, so a
rate-limited burst retries in lockstep, and with spacing in place the
result is 12/12 either way.

The PMC ID Converter is not sent the key: it is a separate service with
no documented api_key support, and attaching one would also pace it at
the keyed rate.
…ctual starts

Two defects found by review, both with repros.

send() cleared the abort timer as soon as fetch resolved, which is when
headers arrive, so the body read ran unprotected. A stalled body hung the
tool call forever where main aborted it after the request timeout. send()
now takes the reader and awaits it inside the try, which also closes the
same pre-existing hole in fetchText.

The gate reserved absolute wake times upfront. One long tick left every
reservation overdue and the whole burst then started at once: measured
0ms gaps after a 700ms event-loop block. Each waiter now measures from
the previous request's actual start.

Tests were passing with the gate disabled. Mutating ANONYMOUS_MIN_GAP_MS
to 0 or dropping the ID Converter host went undetected. Cover the
anonymous rate, the ID Converter host, loop-block resilience, and the
body-read timeout, and add scripts/ncbi-burst-check.mjs so the burst is
reproducible.
The budget is per-IP, so gating PubMed alone left the reported failure in
place: a turn mixing pubmed with clinvar or geo still put 21 requests into
one rolling second against a ceiling of 3. Route the specialty, variants,
and omics-archive fetch helpers through the same gate. They carry no API
key, and the interval is read from the outgoing URL, so they pace at the
anonymous rate rather than the keyed one. Non-NCBI hosts still return
immediately, so their other backends are unaffected.

Add NCBI_MIN_REQUEST_GAP_MS to override the interval, for shared or
institutional IPs that need more room.

The timeout test asserted only that a stalled request had not resolved,
which the regression also satisfies by hanging, so it passed with the bug
reintroduced. Give it a real budget through a test seam and assert the
rejection, racing a deadline so a regression fails the case instead of
wedging the run.

Live, twelve concurrent calls split between pubmed and clinvar: 12/12 with
no rate limiting.
@vercel

vercel Bot commented Aug 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
feynman Ready Ready Preview Aug 21, 2026 3:44am

Request Review

@advaitpaliwal
Advait Paliwal (advaitpaliwal) merged commit dba3f15 into main Aug 21, 2026
12 checks passed
@advaitpaliwal
Advait Paliwal (advaitpaliwal) deleted the codex/ncbi-rate-limit-237 branch August 21, 2026 04:06
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.

Concurrent PubMed requests exceed the NCBI rate limit during /lit

2 participants