Skip to content

fix(llm): retry statusless mid-stream provider errors (quota/billing/transient)#790

Merged
0xallam merged 2 commits into
mainfrom
devin/1784230794-isolate-subagent-model-errors
Jul 17, 2026
Merged

fix(llm): retry statusless mid-stream provider errors (quota/billing/transient)#790
0xallam merged 2 commits into
mainfrom
devin/1784230794-isolate-subagent-model-errors

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Provider errors that surface mid-stream as a bare openai.APIError with no HTTP status code (the SSE data.error path in openai/_streaming.py — the request already returned 200 OK, then the error appears inside the stream) were getting zero retries and failing on the first attempt. Quota/billing exhaustion is the common case, but so is any transient mid-stream blip.

The SDK retry policy only retried errors with a known status code:

policy=retry_policies.any(
    retry_policies.provider_suggested(),
    retry_policies.network_error(),
    retry_policies.http_status((429, 500, 502, 503, 504)),  # status_code is None -> skipped
)

The pre-SDK engine instead retried on code is None or litellm._should_retry(code) — i.e. it did retry statusless errors. Restore that by adding a statusless retry policy:

def _retry_statusless_provider_errors(context):
    n = context.normalized
    if n.is_abort:            # never retry a user cancellation
        return False
    return n.status_code is None

added to DEFAULT_MODEL_RETRY.policy. Retry count (5) and backoff (2→90s, ~62s total) are unchanged. The runner still refuses to replay a stream once content has been emitted (emitted_retry_unsafe_event), so this only retries statusless failures that occur before any content streams — the common shape for quota/rate errors — and a user abort is never retried.

If the provider is genuinely exhausted, the error still propagates and the scan fails after retries — intentionally not a graceful/resumable stop (non-interactive SaaS runs can't be resumed interactively). The pre-existing persistent-RateLimitError handling in run_strix_scan is left untouched.

Testing

  • tests/test_model_retry.py (new): drives the composed DEFAULT_MODEL_RETRY policy — statusless → retry, statusless abort → no retry, 400 → no retry, 429/5xx → retry.
  • Smoke-tested the real SDK decision path (agents.run_internal.model_retry._evaluate_retry) with DEFAULT_MODEL_RETRY: statusless-before-content → retry=True (delay 2s), attempt 2 → 4s, attempt > max(5) → False, statusless-after-content → False (SDK replay guard), 400 → False. Before/after: with the old policy the statusless quota error returns retry=False; with this PR it returns retry=True.
  • ruff, mypy, and the suite pass locally. (Two pre-existing failures in tests/test_runner_root_prompt.py are unrelated — they fail identically on main, from a test fixture missing settings.runtime.)

Link to Devin session: https://app.devin.ai/sessions/dad023e379e942f287bcf6822463b7a4
Requested by: @0xallam

@0xallam 0xallam self-assigned this Jul 16, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR restores retries for statusless provider errors raised before streamed content is emitted. The main changes are:

  • Adds an abort-aware retry policy for errors without an HTTP status.
  • Composes the policy into the default model retry settings.
  • Adds tests for statusless, aborted, client, rate-limit, and server errors.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the updated retry policy or its tests.

Important Files Changed

Filename Overview
strix/config/models.py Adds statusless provider errors to the default retry policy while excluding user aborts.
tests/test_model_retry.py Adds focused coverage for the updated model retry decisions.

Reviews (4): Last reviewed commit: "fix(llm): retry statusless mid-stream pr..." | Re-trigger Greptile

Comment thread strix/core/runner.py Outdated
Comment thread tests/test_runner_rate_limit.py Outdated
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread strix/core/runner.py Outdated
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

@devin-ai-integration devin-ai-integration Bot changed the title fix(runtime): stop the scan gracefully on provider quota/billing exhaustion fix(llm,runtime): retry statusless provider errors, then stop the scan gracefully on quota/billing exhaustion Jul 17, 2026
The SDK's http_status retry policy only retries errors carrying a known
HTTP status code, but quota/billing (and other provider-side) failures
often surface inside a streamed response as a bare error with no status
code, so they were failing on the first attempt. Add a statusless retry
policy to DEFAULT_MODEL_RETRY so they are retried (before any content is
streamed; user aborts are never retried), restoring the pre-SDK engine's
resilience. If the provider is genuinely exhausted, the error still
propagates and fails the scan after retries.
@devin-ai-integration
devin-ai-integration Bot force-pushed the devin/1784230794-isolate-subagent-model-errors branch from c13b1dc to 87f24e2 Compare July 17, 2026 23:35
@devin-ai-integration devin-ai-integration Bot changed the title fix(llm,runtime): retry statusless provider errors, then stop the scan gracefully on quota/billing exhaustion fix(llm): retry statusless mid-stream provider errors (quota/billing/transient) Jul 17, 2026
@0xallam

0xallam commented Jul 17, 2026

Copy link
Copy Markdown
Member

@greptile

The SDK's http_status retry policy only retries errors carrying a known
HTTP status code, but quota/billing (and other provider-side) failures
often surface inside a streamed response as a bare error with no status
code, so they were failing on the first attempt. Add a statusless retry
policy to DEFAULT_MODEL_RETRY (retry count and backoff unchanged) so they
are retried before a genuine exhaustion fails the run; user aborts are
never retried.
@0xallam
0xallam merged commit 9aa151c into main Jul 17, 2026
1 check passed
@0xallam
0xallam deleted the devin/1784230794-isolate-subagent-model-errors branch July 17, 2026 23:47
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