Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5ecbebb
fix(strix): route openai-direct fallback to the OpenAI API base
seonghobae Aug 24, 2026
84eb379
fix(strix): wire direct OpenAI fallback endpoint
seonghobae Aug 24, 2026
689212b
test(strix): pin direct-OpenAI fallback base routing and default-endp…
seonghobae Aug 25, 2026
859ab5a
Merge branch 'main' into fix/strix-openai-fallback-api-base
seonghobae Aug 25, 2026
7c596f3
test(strix): align remaining gpt-5.6-luna expectations with the gpt-5…
seonghobae Aug 25, 2026
2d98237
fix(opencode-review): complete gpt-5.6-luna to gpt-5.4 rename across …
seonghobae Aug 25, 2026
9288031
fix(strix): provision OpenAI fallback credentials for github_models p…
seonghobae Aug 25, 2026
91c79dc
fix(opencode-review): restore deduplicated changelog entries and rena…
seonghobae Aug 25, 2026
c5f1c74
test: repin review workflow contract to exact blob
seonghobae Aug 25, 2026
26f96e2
fix(strix): route direct OpenAI fallback through its API base
seonghobae Aug 25, 2026
3c7f430
fix(strix): preserve standalone custom OpenAI bases
seonghobae Aug 25, 2026
0547c6b
fix(strix): reject foreign GitHub Models fallback bases
seonghobae Aug 25, 2026
4fc261f
fix(strix): reject known foreign fallback bases
seonghobae Aug 25, 2026
91b08f4
Merge branch 'main' into fix/strix-openai-fallback-api-base-v2
opencode-agent[bot] Aug 25, 2026
a1408f5
Merge branch 'main' into fix/strix-openai-fallback-api-base-v2
opencode-agent[bot] Aug 25, 2026
40955ba
Merge main into Strix fallback API-base fix
seonghobae Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/opencode-review-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4477,6 +4477,10 @@ jobs:
# so the OpenRouter slots use cheap paid models billed against the
# org's OpenRouter credits), then the full-size GPT-4.1 long-context
# endpoint and provider-specific GPT/o3 fallbacks.
# The direct-OpenAI slot runs GPT-5.4: the current valid model name
# on the direct OpenAI platform (the retired gpt-5.6-luna catalog
# entry returned 404s), kept cost-efficient against the org's
# OpenAI budget between top-ups.
OPENCODE_MODEL_CANDIDATES: "${{ needs.validate-pr-metadata.outputs.is_private == 'false' && 'nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 nvidia-nim/nvidia/llama-3.1-nemotron-ultra-253b-v1 nvidia-nim/nvidia/nemotron-3-super-120b-a12b nvidia-nim/nvidia/nemotron-3-ultra-550b-a55b nvidia-nim/meta/llama-3.3-70b-instruct nvidia-nim/deepseek-ai/deepseek-v4-pro nvidia-nim/mistralai/codestral-22b-instruct-v0.1 opencode-free/nemotron-3-ultra-free opencode-free/deepseek-v4-flash-free opencode-free/north-mini-code-free opencode-free/laguna-s-2.1-free opencode-free/ling-3.0-flash-free opencode-free/big-pickle opencode-free/mimo-v2.5-free opencode-free/hy3-free opencode-free/minimax-m3-free opencode-free/glm-5-free opencode-free/kimi-k2.5-free opencode-free/qwen3.6-plus-free ' || '' }}opencode/gpt-5.6-terra github-models/deepseek/deepseek-v3-0324 openai/gpt-5.4 openrouter/deepseek/deepseek-v3.2 openrouter/qwen/qwen3-coder github-models/openai/gpt-4.1 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3 github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1"
# One attempt per model, then fall through to the next model. Retrying
# the SAME model 5x let a rate-limited/hung leader consume the whole
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,10 @@ jobs:
echo "LLM_API_BASE_FILE=$llm_api_base_file" >> "$GITHUB_ENV"

- name: Prepare GitHub Models fallback credentials
if: steps.gate.outputs.provider_mode == 'openai_direct' || steps.gate.outputs.provider_mode == 'openrouter' || steps.gate.outputs.provider_mode == 'nvidia_nim'
# github_models is included because its STRIX_FALLBACK_MODELS chain
# ends in openai-direct/gpt-5.4, which needs the direct-OpenAI key and
# API base to authenticate and route after the primary is exhausted.
if: steps.gate.outputs.provider_mode == 'openai_direct' || steps.gate.outputs.provider_mode == 'openrouter' || steps.gate.outputs.provider_mode == 'nvidia_nim' || steps.gate.outputs.provider_mode == 'github_models'
Comment thread
seonghobae marked this conversation as resolved.
env:
GITHUB_MODELS_FALLBACK_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}
OPENAI_FALLBACK_KEY: ${{ secrets.STRIX_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
Expand Down Expand Up @@ -725,6 +728,9 @@ jobs:
openai_fallback_key_file="$RUNNER_TEMP/openai_fallback_key.txt"
printf '%s' "$openai_trimmed" > "$openai_fallback_key_file"
echo "STRIX_OPENAI_FALLBACK_KEY_FILE=$openai_fallback_key_file" >> "$GITHUB_ENV"
openai_fallback_api_base_file="$RUNNER_TEMP/openai_fallback_api_base.txt"
printf '%s' 'https://api.openai.com/v1' > "$openai_fallback_api_base_file"
echo "STRIX_OPENAI_FALLBACK_API_BASE_FILE=$openai_fallback_api_base_file" >> "$GITHUB_ENV"
fi

- name: Prepare Vertex AI credentials
Expand Down Expand Up @@ -857,6 +863,7 @@ jobs:
STRIX_GITHUB_MODELS_API_BASE_FILE: ${{ env.STRIX_GITHUB_MODELS_API_BASE_FILE }}
STRIX_GITHUB_MODELS_KEY_FILE: ${{ env.STRIX_GITHUB_MODELS_KEY_FILE }}
STRIX_OPENAI_FALLBACK_KEY_FILE: ${{ env.STRIX_OPENAI_FALLBACK_KEY_FILE }}
STRIX_OPENAI_FALLBACK_API_BASE_FILE: ${{ env.STRIX_OPENAI_FALLBACK_API_BASE_FILE }}
STRIX_FAIL_ON_PROVIDER_SIGNAL: "1"
STRIX_VERTEX_FALLBACK_MODELS: ""
NPM_CONFIG_IGNORE_SCRIPTS: "true"
Expand Down
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ this file. The format follows Keep a Changelog, and versioned releases follow
Semantic Versioning where the repository publishes a release.

## [Unreleased]

- Route Strix cross-provider fallbacks to explicit direct-OpenAI models
(`openai-direct/...`) through the OpenAI inference endpoint instead of
inheriting the primary provider's `LLM_API_BASE`: the workflow now
provisions `STRIX_OPENAI_FALLBACK_API_BASE_FILE`
(`https://api.openai.com/v1`), the gate prefers it for explicit OpenAI
fallback models and otherwise resolves no override so litellm defaults to
OpenAI, and a non-https override fails configuration. This removes the
NVIDIA-NIM-edge `404 page not found` that made the contracted final
fallback unreachable after NIM exhaustion.
- Align stale `gpt-5.6-luna` test expectations with the valid `gpt-5.4`
contract left behind by the earlier model rename.
- Honor each trusted base project's exact, integrity-bearing pnpm
`packageManager` specification in OpenCode coverage images through the pinned
Node distribution's Corepack runtime, instead of admitting the specification
Expand Down
74 changes: 74 additions & 0 deletions docs/doctoring/strix-openai-fallback-api-base-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Strix direct-OpenAI fallback API-base routing: evidence and design record

## Decision

A Strix cross-provider fallback to an explicit direct-OpenAI model
(`openai-direct/...` or `openai_direct/...`) must route through the OpenAI
inference endpoint, never through the primary provider's `LLM_API_BASE`. The
gate now prefers an explicit `STRIX_OPENAI_FALLBACK_API_BASE_FILE` for such
models and, when the override is absent, resolves no API-base override so
litellm uses its default `https://api.openai.com/v1` endpoint.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

The central workflow writes `https://api.openai.com/v1` into
`$RUNNER_TEMP/openai_fallback_api_base.txt` and exports
`STRIX_OPENAI_FALLBACK_API_BASE_FILE` whenever it publishes the OpenAI
fallback key file, so every provider chain that ends in
`openai-direct/gpt-5.4` (NVIDIA NIM primary, OpenRouter primary,
GitHub Models primary) inherits correct routing automatically.

## Failure this fixes

Required-CI evidence (BandScope PR #1021 strix run 32800796577, 2026-08-25)
showed the NVIDIA NIM primary and first fallback exhausting provider
availability, then the contracted final fallback `openai-direct/gpt-5.4`
failing with a plain-text gateway error:

```text
LLM CONNECTION FAILED
Could not establish connection to the language model.
Error: 404 page not found
```

Root cause: with `provider_mode=nvidia_nim`, the workflow sets
`LLM_API_BASE_FILE=https://integrate.api.nvidia.com/v1`. The gate reused that
base for the openai-direct fallback child, so litellm sent OpenAI requests to
the NVIDIA NIM edge, whose Go gateway answered `404 page not found`. The
fallback key was already routed correctly (`STRIX_OPENAI_FALLBACK_KEY_FILE`);
only the base URL leaked from the primary provider. Because no vulnerability
report artifact was produced, the gate failed closed — correct policy on an
incomplete scan, but caused by routing rather than by any repository finding.

## Trust boundary

The override is a runner-provisioned regular file under `$RUNNER_TEMP`,
resolved through the same `resolve_trusted_input_file` boundary as the other
API-base files: it must be a regular non-symlink file inside the trusted input
root, must trim to a single `https://` URL, and must not contain whitespace or
control characters. Absent or empty overrides fall back to litellm's default
endpoint instead of failing configuration, preserving standalone local gate
runs where no workflow provisioning exists.

## Verification contract

Regression evidence proves that:

1. with a NVIDIA NIM primary base configured, `openai-direct/gpt-5.4`
resolves through the explicit OpenAI fallback base when provided;
2. without an override, the resolver returns no base so litellm defaults to
`https://api.openai.com/v1`;
3. NVIDIA NIM primary attempts keep resolving through the NIM edge;
4. `github_models/*` fallbacks keep their dedicated GitHub Models endpoint;
5. a non-https override fails configuration (exit 2) instead of scanning;
6. the workflow provisions the override file and passes it into the gate env;
7. the required-workflow smoke contract pins both sides of the wiring; and
8. the stale `gpt-5.6-luna` expectations left behind by the model rename are
aligned with the valid `gpt-5.4` contract in queue-contract tests.

## Limitations

This change restores reachability of the final fallback; it does not create
OpenAI quota. If the OpenAI key is absent or exhausted after NIM exhaustion,
the gate still fails closed as provider-unavailable — by design, because no
complete authoritative scan exists. Hosted model catalogs may also change
independently of this repository; model-name updates remain manual contract
changes reviewed through CI.
21 changes: 13 additions & 8 deletions scripts/ci/strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2426,15 +2426,20 @@ resolved_llm_api_base_for_model() {
if is_vertex_model "$model"; then
return 0
fi
if is_explicit_openai_model "$model" && ! is_explicit_openai_model "$PRIMARY_MODEL"; then
# A direct-OpenAI fallback must not inherit a foreign primary provider's
# endpoint (for example NVIDIA NIM or OpenRouter).
return 0
fi

local api_base_file="$LLM_API_BASE_FILE"
local api_base_file="${LLM_API_BASE_FILE:-}"
local api_base_file_name="LLM_API_BASE_FILE"
if is_github_models_model "$model" && [ -n "${STRIX_GITHUB_MODELS_API_BASE_FILE:-}" ]; then
if is_explicit_openai_model "$model" && [ -n "${STRIX_OPENAI_FALLBACK_API_BASE_FILE:-}" ]; then
# Cross-provider fallback: openai-direct/* candidates must reach the
# direct OpenAI API even when the primary provider selected a
# different LLM_API_BASE_FILE endpoint (e.g. NVIDIA NIM). Without
# this the fallback hits the primary gateway and 404s.
api_base_file="$STRIX_OPENAI_FALLBACK_API_BASE_FILE"
api_base_file_name="STRIX_OPENAI_FALLBACK_API_BASE_FILE"
# The workflow always provisions this file for cross-provider fallbacks.
# In standalone runs, an explicitly supplied LLM_API_BASE_FILE remains
# a caller-owned custom OpenAI-compatible endpoint rather than being
# silently discarded.
elif is_github_models_model "$model" && [ -n "${STRIX_GITHUB_MODELS_API_BASE_FILE:-}" ]; then
# Cross-provider fallback: when the active primary provider uses a
# different API base (for example OpenRouter), github_models/* fallback
# attempts must still route through the GitHub Models inference endpoint.
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Expand Down
2 changes: 2 additions & 0 deletions scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ assert_strix_workflow_pr_trigger_hardened() {
assert_file_contains "$workflow_file" "steps.gate.outputs.provider_mode == 'nvidia_nim' && 'nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 openai-direct/gpt-5.4'" "strix workflow gives NVIDIA NIM scans contracted fallbacks"
assert_file_not_contains "$workflow_file" "STRIX_FALLBACK_MODELS: \${{ steps.gate.outputs.provider_mode == 'github_models' && 'github_models/openai/o3" "strix workflow fallback list must not depend on GitHub Models, which is in platform-wide retirement"
assert_file_contains "$workflow_file" "Prepare GitHub Models fallback credentials" "strix workflow provisions GitHub Models fallback credentials for direct-OpenAI scans"
assert_file_contains "$workflow_file" "STRIX_OPENAI_FALLBACK_API_BASE_FILE" "strix workflow routes direct-OpenAI fallbacks through a trusted API base file"
assert_file_contains "$workflow_file" "https://api.openai.com/v1" "strix workflow uses the OpenAI platform endpoint for direct fallbacks"
assert_file_contains "$GATE_SCRIPT" "STRIX_GITHUB_MODELS_KEY_FILE" "strix gate reads the optional GitHub Models fallback key file"
assert_file_contains "$GATE_SCRIPT" "STRIX_GITHUB_MODELS_API_BASE_FILE" "strix gate routes github_models fallback models through the GitHub Models endpoint"
assert_file_not_contains "$workflow_file" 'github_models/deepseek/deepseek-r1-0528 | github_models/deepseek/deepseek-v3-0324)' "strix workflow keeps DeepSeek GitHub Models restricted to fallback-only routing"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_pr_review_autofix_nvidia_nim_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md")
CHANGELOG = Path("CHANGELOG.md")
REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml")
REVIEW_DISPATCH_BLOB_SHA = "ed3f7b44f9afdd6ab295426e5d0440aeca6bdfb5"
REVIEW_DISPATCH_BLOB_SHA = "478788d41ed3e0d8389292a059d225336a925830"


def _workflow_text(path: Path) -> str:
Expand Down
Loading
Loading