Skip to content

fix(strix): retry transient OpenRouter upstream 502s - #1359

Open
seonghobae wants to merge 2 commits into
mainfrom
fix/strix-openrouter-502-transient-retry
Open

fix(strix): retry transient OpenRouter upstream 502s#1359
seonghobae wants to merge 2 commits into
mainfrom
fix/strix-openrouter-502-transient-retry

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • classify OpenRouter upstream 502 responses with provider metadata as transient service unavailability
  • reuse the existing bounded STRIX_TRANSIENT_RETRY_PER_MODEL, backoff, and total-budget path for fallback models
  • keep non-provider target output, vulnerability evidence, timeout deadlines, and fallback exhaustion fail-closed

Root cause and exact evidence

#1357 merged as 31e5f5337d8a8d844c456fe03f123c51b62416c9. In repository-dispatch run 33006163284, the primary NVIDIA NIM model received three bounded rate-limit attempts. The first fallback, openrouter/free, then returned:

litellm.APIError: APIError: OpenrouterException
{"error":{"message":"Invalid URL:","code":502,"metadata":{"provider_name":"Stealth"}}}

It was attempted once and the gate immediately moved to direct OpenAI, which then used the same bounded retry helper three times before failing on insufficient_quota / credit_balance_exhausted.

Both the primary and every fallback already call run_strix_with_transient_retry; the defect was not primary-only retry wiring. is_llm_service_unavailable_error recognized LiteLLM ServiceUnavailableError/503 shapes but not this OpenRouter APIError/502 shape, so the shared transient helper declined a same-model retry.

The new classifier requires all of the following: LiteLLM APIError with OpenrouterException, JSON code 502, and non-empty OpenRouter metadata.provider_name. A target-like 502 without the provider exception remains non-retryable. No ephemeral explicit model is pinned and no provider failure becomes passing evidence.

Gap: G-03 in docs/product-technical-gap-baseline.md.

Verification

  • Regression-first red fixture: expected 3 calls but observed only vertex_ai/missing-primary|openrouter/free (2 calls), exit 1.
  • STRIX_TEST_CASE_FILTER=openrouter-502-fallback-retry-same-model-success STRIX_TEST_TRACE_CASES=1 bash scripts/ci/test_strix_quick_gate.sh
  • STRIX_TEST_CASE_FILTER=service-unavailable-no-llm-marker-nonrecoverable bash scripts/ci/test_strix_quick_gate.sh
  • bash scripts/ci/strix_required_workflow_smoke.sh
  • bash -n scripts/ci/strix_quick_gate.sh
  • bash -n scripts/ci/test_strix_quick_gate.sh
  • git diff --check

Open in Devin Review

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 46 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f612cab-71f2-4014-9037-289053c6b334

📥 Commits

Reviewing files that changed from the base of the PR and between 31e5f53 and 31594fc.

📒 Files selected for processing (2)
  • scripts/ci/strix_quick_gate.sh
  • scripts/ci/test_strix_quick_gate.sh

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Fresh cross-repository reproduction from ContextualWisdomLab/OriginWeave PR #218 exact head 911ea33d8a5aca7673307bb6fdcad4b450f5c111: required Strix run 33009412138, job 98311486189, trusted workflow SHA 31e5f5337d8a8d844c456fe03f123c51b62416c9 failed closed with the same provider chain. NVIDIA NIM returned HTTP 429 on all 3 bounded attempts; openrouter/free then returned litellm.APIError: APIError: OpenrouterException with JSON code: 502 and metadata.provider_name: "Stealth" and was attempted only once; direct OpenAI then exhausted all 3 bounded attempts with insufficient_quota / credit_balance_exhausted. No Strix vulnerability report was produced. This independently confirms the OpenRouter-502 classifier/retry gap that #1359 owns; no OriginWeave-local product or workflow workaround is appropriate.

Copy link
Copy Markdown
Contributor Author

Fresh ScopeWeave consumer canary confirms this owner path is production-relevant on the protected-main workflow source 31e5f5337d8a8d844c456fe03f123c51b62416c9.

Exact evidence:

  • consumer: ContextualWisdomLab/scopeweave#610
  • PR head: f60715267a742e299468c34051a1720be11ad26b
  • protected base: develop@2c328875e00e86537df3e965170be80532571cad
  • required Strix workflow run: 32996488630
  • Strix job/check: 98318956632
  • central workflow source: .github/.github/workflows/strix.yml@31e5f5337d8a8d844c456fe03f123c51b62416c9
  • primary nvidia_nim/nvidia/nemotron-3-super-120b-a12b: three bounded 429 attempts
  • first fallback openrouter/free: litellm.APIError: APIError: OpenrouterException with {"error":{"message":"Invalid URL:","code":502,"metadata":{"provider_name":"Stealth"}}}
  • direct OpenAI gpt-5.4: three bounded 429 insufficient_quota / credit_balance_exhausted attempts
  • no structured vulnerability report; final typed result STRIX_PROVIDER_UNAVAILABLE; workflow correctly remained non-passing.

This reproduces the same OpenRouter-502 classification gap that #1359 repairs, so use ScopeWeave #610 as an unchanged downstream GREEN canary after the owner fix integrates. Acceptance should require a real complete Strix report if governed fallback is available, otherwise an explicit non-passing provider-unavailable result; never count infrastructure exhaustion as merge-ready security evidence.

Current-head review note also matters before owner integration: the Devin finding on scripts/ci/strix_quick_gate.sh says the metadata.provider_name regex can be order/nesting-sensitive. Please preserve the narrow anti-spoof boundary while making the classifier robust to valid OpenRouter metadata ordering, then regenerate exact-head evidence.

Copy link
Copy Markdown
Contributor Author

New owner-repository canary makes this an upstream integration blocker, not only downstream consumer friction.

Fresh exact evidence:

The failure chain is the same causal class #1359 owns: NVIDIA NIM Nemotron failed three bounded attempts with HTTP 429; openrouter/free then failed once with LiteLLM APIError: OpenrouterException, code 502, metadata.provider_name="Stealth", Invalid URL; direct OpenAI gpt-5.4 then failed three bounded attempts with insufficient_quota / credit_balance_exhausted. No structured vulnerability report was produced and the gate correctly failed closed as STRIX_PROVIDER_UNAVAILABLE.

So #897 cannot obtain merge-grade current-head security evidence while the OpenRouter 502 same-model retry gap remains. After #1359 addresses the still-unresolved Devin metadata-order/nesting finding and integrates, use an unchanged/current #897 head as a central canary: require an actual complete Strix vulnerability result (or remain non-passing on provider exhaustion), never an infrastructure-only green.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment on lines +3027 to +3030
if grep -Eiq 'Vulnerabilities[[:space:]]+[1-9][0-9]*' "$STRIX_LOG" ||
has_blocking_vulnerability_reports; then
return 1
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Vulnerability guard suppresses retry for every transient family

The new guard in is_transient_same_model_retry_error (strix_quick_gate.sh) blocks same-model retry for every transient family, not just OpenRouter 502, whenever the log shows Vulnerabilities N (N>=1) or a blocking report exists. This scope is broader than the PR title implies. It reads as intentional and no fixture regresses.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +2940 to +2942
inline_start = line.find("{", match.end())
fragments = [line[inline_start:]] if inline_start >= 0 else lines[index + 1 : index + 33]
if not fragments or not fragments[0].lstrip().startswith("{"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 OpenRouter 502 JSON detection assumes adjacency

is_openrouter_upstream_502_error (strix_quick_gate.sh) only finds the error JSON when it is inline or begins on the immediately following line. A blank line, traceback, or truncated inline body between the OpenrouterException line and the JSON makes detection silently fail, so the retry never fires. Fixtures keep them adjacent; real logs can differ.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant