Skip to content

Add retry with backoff for transient LLM API failures (429/5xx) - #2

Open
jaiswalakshansh wants to merge 1 commit into
PhonePe:mainfrom
jaiswalakshansh:fix/llm-429-retry-backoff
Open

Add retry with backoff for transient LLM API failures (429/5xx)#2
jaiswalakshansh wants to merge 1 commit into
PhonePe:mainfrom
jaiswalakshansh:fix/llm-429-retry-backoff

Conversation

@jaiswalakshansh

Copy link
Copy Markdown

Problem

LLMClient.chat() throws immediately on any non-2xx response, including HTTP 429 rate-limit errors, with no retry or backoff.

Findings are verified in a sequential loop (TaiEPOC, SemgrepPOC, TruffleHogPOC, PermissionCheckerPOC all call llmClient.chat(...) one flow at a time). Because there is no pacing or retry:

  1. The first verification call consumes the provider's per-minute token budget (TPM).
  2. Every subsequent call in that same minute returns 429 instantly (~50ms each).
  3. All remaining findings fail-fast within the same wall-clock second and are recorded as LLM ERROR - could not verify, skipping.

On lower API tiers (e.g. OpenAI Tier 1, gpt-4o = 30k TPM) a single scan can lose almost every finding to this burst. Observed on a real run: 15 findings, only 1 verified, the other 14 all 429 at the same timestamp.

Fix

Wrap the request in a bounded retry loop inside LLMClient:

  • 5 attempts max.
  • Retries on 429 and 5xx (500/502/503/504/529).
  • Honours the server's Retry-After / retry-after-ms hint when present; otherwise exponential backoff (2s base, 60s cap) with jitter.
  • Non-retryable errors (401/403 and other 4xx) still fail fast — no point retrying an auth error.
  • Network-level IOExceptions (timeouts, resets) are also retried.

This lets the per-minute budget window reset between attempts, so findings get verified instead of being dropped. No API changes — chat() keeps the same signature and throws the same IOException only after retries are exhausted.

Notes

  • No new dependencies (uses java.util.concurrent.ThreadLocalRandom and existing OkHttp/Gson).
  • Thread.sleep interruption is surfaced as an IOException with the interrupt flag restored.
  • Compiled locally with mvn compile.

LLMClient.chat() threw immediately on any non-2xx response, including
HTTP 429 rate-limit errors. Findings are verified in a sequential loop,
so once a provider's per-minute token budget is exhausted the first call
consumes it and every subsequent call fail-fasts with 429 inside the same
minute. On lower API tiers this drops most findings to "LLM ERROR /
could not verify" in a single burst.

Wrap the request in a bounded retry loop (5 attempts) that treats 429 and
5xx (500/502/503/504/529) as transient. Backoff honours the server's
Retry-After / retry-after-ms hint when present, otherwise uses exponential
backoff (2s base, 60s cap) with jitter. Non-retryable errors (401/403 and
other 4xx) still fail fast. Network-level IOExceptions are also retried.
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