Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
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
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,18 @@ 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 a provider-specific primary base: the workflow now provisions
`STRIX_OPENAI_FALLBACK_API_BASE_FILE` (`https://api.openai.com/v1`), while
standalone caller-supplied `LLM_API_BASE_FILE` values remain honored for
OpenAI-compatible endpoints. Known GitHub Models, NVIDIA NIM, and OpenRouter
bases are never inherited, and LiteLLM uses native OpenAI defaults only when
no base is supplied. 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
83 changes: 83 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,83 @@
# 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. In standalone runs, a caller-supplied `LLM_API_BASE_FILE` remains in
force for an OpenAI-compatible endpoint; known GitHub Models, NVIDIA NIM, and
OpenRouter primary endpoints are not inherited. Litellm uses its default
`https://api.openai.com/v1` endpoint only when no base file is supplied (or
when that provider-specific base is rejected).

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 preserve a caller-supplied
`LLM_API_BASE_FILE` for standalone local gate runs; when both files are absent,
litellm selects its default endpoint. Known GitHub Models, NVIDIA NIM, and
OpenRouter bases are explicitly rejected for a direct OpenAI model so a
missing OpenAI key remains a provider-unavailable outcome instead of a
configuration error.

## 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 either base file, the resolver returns no base so litellm defaults
to `https://api.openai.com/v1`;
3. a standalone caller-supplied custom `LLM_API_BASE_FILE` remains effective;
4. known GitHub Models, NVIDIA NIM, and OpenRouter primary bases are not
inherited by a direct-OpenAI fallback;
5. NVIDIA NIM primary attempts keep resolving through the NIM edge;
6. `github_models/*` fallbacks keep their dedicated GitHub Models endpoint;
7. a non-https override fails configuration (exit 2) instead of scanning;
8. the workflow provisions the override file and passes it into the gate env;
9. the required-workflow smoke contract pins both sides of the wiring; and
10. 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.
41 changes: 33 additions & 8 deletions scripts/ci/strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,17 @@ is_github_models_api_base() {
esac
}

is_known_foreign_provider_api_base() {
case "$1" in
https://models.github.ai/* | https://integrate.api.nvidia.com/* | https://openrouter.ai/*)
return 0
;;
*)
return 1
;;
esac
}

PRIMARY_MODEL="$(normalize_model "$STRIX_LLM")"
if [ "$PRIMARY_MODEL" != "$STRIX_LLM" ]; then
echo "Normalized STRIX_LLM to provider-qualified model '$PRIMARY_MODEL'."
Expand Down Expand Up @@ -2426,15 +2437,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 Expand Up @@ -2470,6 +2486,15 @@ resolved_llm_api_base_for_model() {
echo "ERROR: LLM_API_BASE must be an https URL when configured." >&2
return 2
fi
# Never let a known provider-specific base leak into an explicit
# direct-OpenAI fallback when no separate OpenAI override was provisioned.
# Other caller-supplied OpenAI-compatible endpoints remain valid standalone
# configuration and are intentionally preserved.
if is_explicit_openai_model "$model" \
&& [ -z "${STRIX_OPENAI_FALLBACK_API_BASE_FILE:-}" ] \
&& is_known_foreign_provider_api_base "$llm_api_base_value"; then
return 0
fi
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
if is_github_models_api_base "$llm_api_base_value" && ! is_github_models_api_compatible_model "$model"; then
echo "ERROR: LLM_API_BASE may route through GitHub Models only when STRIX_LLM uses a GitHub Models-compatible model." >&2
return 2
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
Loading
Loading