From 7471787718c89aa0d192ea2b73157a523b65d0d2 Mon Sep 17 00:00:00 2001 From: seonghobae Date: Mon, 24 Aug 2026 18:00:33 +0900 Subject: [PATCH 01/13] fix(strix): route direct-OpenAI fallback models to the OpenAI platform endpoint resolved_llm_api_base_for_model() inherited the primary provider's LLM_API_BASE_FILE for every non-Vertex model. When NVIDIA NIM rate-limited (HTTP 429) and the chain fell back to openai-direct/gpt-5.6-luna, the gate sent an OpenAI model and key to https://integrate.api.nvidia.com/v1, which answered '404 page not found' and failed the whole scan closed. Explicit direct-OpenAI models (openai_direct/* and openai-direct/*) now resolve to no custom API base, so litellm uses the OpenAI platform endpoint with STRIX_OPENAI_FALLBACK_KEY. GitHub Models base guards are unchanged. --- scripts/ci/strix_quick_gate.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 36ec3e5f8..1d07ef915 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -2415,6 +2415,15 @@ resolved_llm_api_base_for_model() { return 0 fi + if is_explicit_openai_model "$model"; then + # Cross-provider fallback: direct-OpenAI models must always use the + # OpenAI platform endpoint. Inheriting the primary provider's API + # base (NVIDIA NIM, OpenRouter, GitHub Models) sends an OpenAI + # model and key to a foreign host, which answers "404 page not + # found" and turns every rate-limit outage into a failed fallback. + return 0 + fi + 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 From 3d18db7b9eaa19660a80528a05eac682267a4359 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 18:18:36 +0900 Subject: [PATCH 02/13] fix(strix): fail closed without fallback credentials --- scripts/ci/strix_quick_gate.sh | 24 ++++++++++----- scripts/ci/test_strix_quick_gate.sh | 48 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 1d07ef915..14d55df0d 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -384,8 +384,9 @@ fi # models (openai-direct/... or openai_direct/...). When the primary model runs # against NVIDIA NIM, OpenRouter, or GitHub Models, its LLM_API_KEY cannot # authenticate a direct-OpenAI fallback; this file carries the OpenAI key. -# Optional: without it, explicit direct-OpenAI models keep using LLM_API_KEY, -# which is correct whenever the primary already runs against direct OpenAI. +# When the primary already runs against direct OpenAI, its LLM_API_KEY is safe +# to reuse for direct-OpenAI fallback models. A cross-provider fallback without +# this key fails closed instead of receiving the primary provider's credential. STRIX_OPENAI_FALLBACK_KEY_FILE="${STRIX_OPENAI_FALLBACK_KEY_FILE:-}" if [ -n "$STRIX_OPENAI_FALLBACK_KEY_FILE" ] && { [ ! -f "$STRIX_OPENAI_FALLBACK_KEY_FILE" ] || [ -L "$STRIX_OPENAI_FALLBACK_KEY_FILE" ]; }; then echo "ERROR: STRIX_OPENAI_FALLBACK_KEY_FILE must reference a regular file containing the API key." >&2 @@ -2554,11 +2555,20 @@ run_strix_once() { # with the GitHub Models token, not the direct-OpenAI key. child_llm_api_key="$STRIX_GITHUB_MODELS_KEY" fi - if is_explicit_openai_model "$model" && [ -n "$STRIX_OPENAI_FALLBACK_KEY" ]; then - # Cross-provider fallback: explicit direct-OpenAI models - # authenticate with the OpenAI key, not the primary provider's - # key (NVIDIA NIM, OpenRouter, or GitHub Models). - child_llm_api_key="$STRIX_OPENAI_FALLBACK_KEY" + if is_explicit_openai_model "$model"; then + if is_explicit_openai_model "$PRIMARY_MODEL"; then + # Same-provider fallback: the primary direct-OpenAI key is + # valid for every direct-OpenAI model in this chain. + : + elif [ -n "$STRIX_OPENAI_FALLBACK_KEY" ]; then + # Cross-provider fallback: explicit direct-OpenAI models + # authenticate with the OpenAI key, not the primary provider's + # key (NVIDIA NIM, OpenRouter, or GitHub Models). + child_llm_api_key="$STRIX_OPENAI_FALLBACK_KEY" + else + echo "ERROR: direct-OpenAI fallback '$model' requires STRIX_OPENAI_FALLBACK_KEY_FILE when the primary model uses another provider." >&2 + return 2 + fi fi fi set -o pipefail diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 945eb3fb3..a457c2878 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -3421,6 +3421,26 @@ REPORT ;; esac ;; + nvidia-nim-quota-openai-direct-fallback-missing-key) + case "${STRIX_LLM:-}" in + nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5) + if [ "${LLM_API_KEY:-}" != "dummy" ]; then + echo "unexpected primary key for NVIDIA NIM (${LLM_API_KEY:-})" >&2 + exit 17 + fi + echo "Error: litellm.RateLimitError: Error code: 429" + exit 1 + ;; + openai/gpt-5.6-luna) + echo "unexpected direct-OpenAI fallback invocation without a fallback key" >&2 + exit 18 + ;; + *) + echo "unexpected model ${STRIX_LLM:-}" >&2 + exit 9 + ;; + esac + ;; vertex-all-notfound) echo "Error: litellm.NotFoundError: Vertex_aiException - x" echo '"status": "NOT_FOUND"' @@ -5648,6 +5668,10 @@ PY env_cmd+=(STRIX_GITHUB_MODELS_API_BASE_FILE="$tmp_dir/github_models_api_base.txt") env_cmd+=(STRIX_GITHUB_MODELS_KEY_FILE="$tmp_dir/github_models_key.txt") fi + if [ "$scenario" = "nvidia-nim-quota-openai-direct-fallback-missing-key" ]; then + # Exercise the cross-provider path with no OpenAI fallback credential. + env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna") + fi if [ "$min_fail_severity" = "__UNSET__" ]; then local next_env_cmd=() local env_pair @@ -5742,6 +5766,7 @@ PY -u STRIX_VERTEX_FALLBACK_MODELS \ -u STRIX_GEMINI_FALLBACK_MODELS \ -u STRIX_FALLBACK_MODELS \ + -u STRIX_OPENAI_FALLBACK_KEY_FILE \ "${env_cmd[@]}" \ bash "./scripts/ci/strix_quick_gate.sh" >"$output_log" 2>&1 ) @@ -6388,6 +6413,17 @@ run_filtered_gate_case_if_requested() { "vertex_ai/report-rate-limit-primary|vertex_ai/fallback-one" \ "|" ;; + nvidia-nim-quota-openai-direct-fallback-missing-key) + run_gate_case "$STRIX_TEST_CASE_FILTER" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "" \ + "2" \ + "ERROR: direct-OpenAI fallback 'openai-direct/gpt-5.6-luna' requires STRIX_OPENAI_FALLBACK_KEY_FILE" \ + "1" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "" \ + "nvidia_nim" + ;; total-timeout) run_total_timeout_case ;; @@ -12514,6 +12550,18 @@ run_gate_case "openai-direct-quota-github-models-fallback-success" \ "" \ "github_models/openai/o3" +# A cross-provider direct-OpenAI fallback must fail closed when its dedicated +# credential is absent; it must never receive the primary NVIDIA key. +run_gate_case "nvidia-nim-quota-openai-direct-fallback-missing-key" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "" \ + "2" \ + "ERROR: direct-OpenAI fallback 'openai-direct/gpt-5.6-luna' requires STRIX_OPENAI_FALLBACK_KEY_FILE" \ + "1" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "" \ + "nvidia_nim" + run_gate_case "github-models-fallback-success-deepseek-v3" \ "vertex_ai/missing-primary" \ "github_models/deepseek/deepseek-r1-0528 github_models/deepseek/deepseek-v3-0324" \ From 26ab3e7310ac4a23f307500f592961318ef9a4d4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 18:46:09 +0900 Subject: [PATCH 03/13] fix(strix): preserve fallback routing contracts --- scripts/ci/strix_quick_gate.sh | 18 ++++--- scripts/ci/test_strix_quick_gate.sh | 82 ++++++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 14 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 14d55df0d..eaba8d94d 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -2416,7 +2416,7 @@ resolved_llm_api_base_for_model() { return 0 fi - if is_explicit_openai_model "$model"; then + if is_explicit_openai_model "$model" && ! is_explicit_openai_model "$PRIMARY_MODEL"; then # Cross-provider fallback: direct-OpenAI models must always use the # OpenAI platform endpoint. Inheriting the primary provider's API # base (NVIDIA NIM, OpenRouter, GitHub Models) sends an OpenAI @@ -2556,15 +2556,16 @@ run_strix_once() { child_llm_api_key="$STRIX_GITHUB_MODELS_KEY" fi if is_explicit_openai_model "$model"; then - if is_explicit_openai_model "$PRIMARY_MODEL"; then - # Same-provider fallback: the primary direct-OpenAI key is - # valid for every direct-OpenAI model in this chain. - : - elif [ -n "$STRIX_OPENAI_FALLBACK_KEY" ]; then + if [ "$model" != "$PRIMARY_MODEL" ] && [ -n "$STRIX_OPENAI_FALLBACK_KEY" ]; then # Cross-provider fallback: explicit direct-OpenAI models # authenticate with the OpenAI key, not the primary provider's - # key (NVIDIA NIM, OpenRouter, or GitHub Models). + # key (NVIDIA NIM, OpenRouter, or GitHub Models). The same + # dedicated key also takes precedence for same-provider fallback. child_llm_api_key="$STRIX_OPENAI_FALLBACK_KEY" + elif is_explicit_openai_model "$PRIMARY_MODEL"; then + # Same-provider fallback: reuse the primary direct-OpenAI key + # only when no dedicated fallback key was configured. + : else echo "ERROR: direct-OpenAI fallback '$model' requires STRIX_OPENAI_FALLBACK_KEY_FILE when the primary model uses another provider." >&2 return 2 @@ -4266,7 +4267,8 @@ run_current_target_scan() { return 0 fi if [ "$fallback_scan_rc" -eq 2 ]; then - return 2 + echo "Skipping fallback model '$candidate' because its provider configuration is invalid; trying the next configured fallback." >&2 + continue fi local strict_fallback_provider_signal=0 diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index a457c2878..6eb6ccbdd 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -3421,6 +3421,30 @@ REPORT ;; esac ;; + same-provider-direct-openai-fallback-key) + case "${STRIX_LLM:-}" in + openai/gpt-5.6-luna) + if [ "${LLM_API_KEY:-}" != "dummy" ]; then + echo "unexpected primary direct-OpenAI key (${LLM_API_KEY:-})" >&2 + exit 19 + fi + echo "Error: litellm.RateLimitError: Error code: 429" + exit 1 + ;; + openai/gpt-5.5) + if [ "${LLM_API_KEY:-}" != "same-provider-fallback-token" ]; then + echo "unexpected same-provider fallback key (${LLM_API_KEY:-})" >&2 + exit 20 + fi + echo "scan ok with same-provider fallback key" + exit 0 + ;; + *) + echo "unexpected model ${STRIX_LLM:-}" >&2 + exit 9 + ;; + esac + ;; nvidia-nim-quota-openai-direct-fallback-missing-key) case "${STRIX_LLM:-}" in nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5) @@ -3435,6 +3459,10 @@ REPORT echo "unexpected direct-OpenAI fallback invocation without a fallback key" >&2 exit 18 ;; + deepseek/deepseek-v3-0324) + echo "scan ok after invalid direct-OpenAI fallback" + exit 0 + ;; *) echo "unexpected model ${STRIX_LLM:-}" >&2 exit 9 @@ -4367,7 +4395,7 @@ EOS echo "Error: deepseek model was rewritten (${STRIX_LLM:-})" >&2 exit 33 ;; - preserve-existing-api-base) + preserve-existing-api-base|preserve-existing-direct-openai-api-base) if [ "${LLM_API_BASE:-}" = "https://preexisting.invalid" ]; then echo "scan ok with preserved api base" exit 0 @@ -5669,8 +5697,14 @@ PY env_cmd+=(STRIX_GITHUB_MODELS_KEY_FILE="$tmp_dir/github_models_key.txt") fi if [ "$scenario" = "nvidia-nim-quota-openai-direct-fallback-missing-key" ]; then - # Exercise the cross-provider path with no OpenAI fallback credential. - env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna") + # Exercise the cross-provider path with no OpenAI fallback credential, + # then prove a later valid fallback is still attempted. + env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna deepseek/deepseek-v3-0324") + fi + if [ "$scenario" = "same-provider-direct-openai-fallback-key" ]; then + printf '%s' 'same-provider-fallback-token' >"$tmp_dir/openai_fallback_key.txt" + env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.5") + env_cmd+=(STRIX_OPENAI_FALLBACK_KEY_FILE="$tmp_dir/openai_fallback_key.txt") fi if [ "$min_fail_severity" = "__UNSET__" ]; then local next_env_cmd=() @@ -6417,13 +6451,37 @@ run_filtered_gate_case_if_requested() { run_gate_case "$STRIX_TEST_CASE_FILTER" \ "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ "" \ + "0" \ + "scan ok after invalid direct-OpenAI fallback" \ "2" \ - "ERROR: direct-OpenAI fallback 'openai-direct/gpt-5.6-luna' requires STRIX_OPENAI_FALLBACK_KEY_FILE" \ - "1" \ - "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5|deepseek/deepseek-v3-0324" \ "" \ "nvidia_nim" ;; + same-provider-direct-openai-fallback-key) + run_gate_case "$STRIX_TEST_CASE_FILTER" \ + "openai_direct/gpt-5.6-luna" \ + "" \ + "0" \ + "scan ok with same-provider fallback key" \ + "2" \ + "openai/gpt-5.6-luna|openai/gpt-5.5" \ + "|" \ + "openai" + ;; + preserve-existing-direct-openai-api-base) + run_gate_case "$STRIX_TEST_CASE_FILTER" \ + "openai_direct/gpt-5.6-luna" \ + "" \ + "0" \ + "scan ok with preserved api base" \ + "1" \ + "openai/gpt-5.6-luna" \ + "https://preexisting.invalid" \ + "openai" \ + "" \ + "https://preexisting.invalid" + ;; total-timeout) run_total_timeout_case ;; @@ -10955,6 +11013,18 @@ run_gate_case "preserve-existing-api-base" \ "" \ "https://preexisting.invalid" +run_gate_case "preserve-existing-direct-openai-api-base" \ + "openai_direct/gpt-5.6-luna" \ + "" \ + "0" \ + "scan ok with preserved api base" \ + "1" \ + "openai/gpt-5.6-luna" \ + "https://preexisting.invalid" \ + "openai" \ + "" \ + "https://preexisting.invalid" + run_gate_case "default-fallback-order-fast-first" \ "vertex_ai/missing-primary" \ "" \ From 9482635445eaa1b12426c57b7919e5774df04b38 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 19:27:29 +0900 Subject: [PATCH 04/13] test(strix): align invalid fallback expectations --- scripts/ci/test_strix_quick_gate.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 6eb6ccbdd..71ef1733b 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -12517,8 +12517,8 @@ run_gate_case "github-models-mistral-prefix-with-api-base-succeeds" \ run_gate_case "github-models-fallback-requires-api-base" \ "vertex_ai/missing-primary" \ "openai/openai/gpt-5.4" \ - "2" \ - "GitHub Models Strix scans require LLM_API_BASE_FILE" \ + "1" \ + "Configured Vertex model and fallback models were unavailable." \ "1" \ "vertex_ai/missing-primary" \ "" \ @@ -12625,10 +12625,10 @@ run_gate_case "openai-direct-quota-github-models-fallback-success" \ run_gate_case "nvidia-nim-quota-openai-direct-fallback-missing-key" \ "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ "" \ + "0" \ + "scan ok after invalid direct-OpenAI fallback" \ "2" \ - "ERROR: direct-OpenAI fallback 'openai-direct/gpt-5.6-luna' requires STRIX_OPENAI_FALLBACK_KEY_FILE" \ - "1" \ - "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5|deepseek/deepseek-v3-0324" \ "" \ "nvidia_nim" From ba7775521e4a0bf41e07a94f1cc089015bc227c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:03:51 +0900 Subject: [PATCH 05/13] fix(strix): preserve fallback configuration errors --- scripts/ci/strix_quick_gate.sh | 6 ++++++ scripts/ci/test_strix_quick_gate.sh | 30 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index eaba8d94d..c00ccdc6b 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -4236,6 +4236,7 @@ run_current_target_scan() { read -r -a FALLBACK_MODELS <<<"$FALLBACK_MODELS_RAW" fallback_tried=0 + local fallback_config_failures=0 for candidate_raw in "${FALLBACK_MODELS[@]}"; do candidate="$(normalize_model "$candidate_raw")" if [ -z "$candidate" ] || [ "$candidate" = "$PRIMARY_MODEL" ]; then @@ -4267,6 +4268,7 @@ run_current_target_scan() { return 0 fi if [ "$fallback_scan_rc" -eq 2 ]; then + fallback_config_failures=$((fallback_config_failures + 1)) echo "Skipping fallback model '$candidate' because its provider configuration is invalid; trying the next configured fallback." >&2 continue fi @@ -4336,6 +4338,10 @@ run_current_target_scan() { fi return 1 fi + if [ "$fallback_config_failures" -eq "$fallback_tried" ]; then + echo "ERROR: All configured fallback models failed provider configuration." >&2 + return 2 + fi if [ "$INFRA_ERROR_DETECTED" -eq 1 ] && [ "$PR_FINDINGS_DECISION" = "allow_baseline" ]; then diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 71ef1733b..d7df86825 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -3469,6 +3469,22 @@ REPORT ;; esac ;; + all-fallbacks-invalid) + case "${STRIX_LLM:-}" in + nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5) + if [ "${LLM_API_KEY:-}" != "dummy" ]; then + echo "unexpected primary key for NVIDIA NIM (${LLM_API_KEY:-})" >&2 + exit 21 + fi + echo "Error: litellm.RateLimitError: Error code: 429" + exit 1 + ;; + *) + echo "unexpected model ${STRIX_LLM:-}" >&2 + exit 22 + ;; + esac + ;; vertex-all-notfound) echo "Error: litellm.NotFoundError: Vertex_aiException - x" echo '"status": "NOT_FOUND"' @@ -5701,6 +5717,9 @@ PY # then prove a later valid fallback is still attempted. env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna deepseek/deepseek-v3-0324") fi + if [ "$scenario" = "all-fallbacks-invalid" ]; then + env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna") + fi if [ "$scenario" = "same-provider-direct-openai-fallback-key" ]; then printf '%s' 'same-provider-fallback-token' >"$tmp_dir/openai_fallback_key.txt" env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.5") @@ -6458,6 +6477,17 @@ run_filtered_gate_case_if_requested() { "" \ "nvidia_nim" ;; + all-fallbacks-invalid) + run_gate_case "$STRIX_TEST_CASE_FILTER" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "" \ + "2" \ + "All configured fallback models failed provider configuration" \ + "1" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "" \ + "nvidia_nim" + ;; same-provider-direct-openai-fallback-key) run_gate_case "$STRIX_TEST_CASE_FILTER" \ "openai_direct/gpt-5.6-luna" \ From 0a4fbca628a02f0791319670b640d065efd67294 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:15:54 +0900 Subject: [PATCH 06/13] test(strix): update configuration-error fallback contract --- scripts/ci/test_strix_quick_gate.sh | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index d7df86825..769c212f8 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -6423,6 +6423,18 @@ run_filtered_gate_case_if_requested() { "deepseek/deepseek-r1-0528 deepseek/deepseek-v3-0324" \ "1" ;; + github-models-fallback-requires-api-base) + run_gate_case "$STRIX_TEST_CASE_FILTER" \ + "vertex_ai/missing-primary" \ + "openai/openai/gpt-5.4" \ + "2" \ + "All configured fallback models failed provider configuration." \ + "1" \ + "vertex_ai/missing-primary" \ + "" \ + "vertex_ai" \ + "" + ;; endpoint-in-excluded-dir) run_gate_case "endpoint-in-excluded-dir" \ "vertex_ai/excluded-dir-primary" \ @@ -12547,8 +12559,8 @@ run_gate_case "github-models-mistral-prefix-with-api-base-succeeds" \ run_gate_case "github-models-fallback-requires-api-base" \ "vertex_ai/missing-primary" \ "openai/openai/gpt-5.4" \ - "1" \ - "Configured Vertex model and fallback models were unavailable." \ + "2" \ + "All configured fallback models failed provider configuration." \ "1" \ "vertex_ai/missing-primary" \ "" \ From fa2370b865764c8b84bcbe6373072fc0ae701415 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:18:10 +0900 Subject: [PATCH 07/13] fix(strix): count attempted fallback models --- scripts/ci/strix_quick_gate.sh | 4 +++- scripts/ci/test_strix_quick_gate.sh | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index c00ccdc6b..c7cd91034 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -4236,6 +4236,7 @@ run_current_target_scan() { read -r -a FALLBACK_MODELS <<<"$FALLBACK_MODELS_RAW" fallback_tried=0 + local fallback_attempts=0 local fallback_config_failures=0 for candidate_raw in "${FALLBACK_MODELS[@]}"; do candidate="$(normalize_model "$candidate_raw")" @@ -4250,6 +4251,7 @@ run_current_target_scan() { fi fallback_tried=1 + fallback_attempts=$((fallback_attempts + 1)) if is_vertex_model "$PRIMARY_MODEL"; then echo "Primary Vertex model unavailable; retrying with fallback '$candidate'." else @@ -4338,7 +4340,7 @@ run_current_target_scan() { fi return 1 fi - if [ "$fallback_config_failures" -eq "$fallback_tried" ]; then + if [ "$fallback_config_failures" -eq "$fallback_attempts" ]; then echo "ERROR: All configured fallback models failed provider configuration." >&2 return 2 fi diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 769c212f8..5d73f0d08 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -5718,7 +5718,7 @@ PY env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna deepseek/deepseek-v3-0324") fi if [ "$scenario" = "all-fallbacks-invalid" ]; then - env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna") + env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna openai_direct/gpt-5.5") fi if [ "$scenario" = "same-provider-direct-openai-fallback-key" ]; then printf '%s' 'same-provider-fallback-token' >"$tmp_dir/openai_fallback_key.txt" From a65fe0ce34fe045e3e56c8276846eed90a629c8d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:27:59 +0900 Subject: [PATCH 08/13] test(strix): cover mixed fallback configuration failures --- scripts/ci/test_strix_quick_gate.sh | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 5d73f0d08..883a8b63a 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -3485,6 +3485,22 @@ REPORT ;; esac ;; + mixed-fallback-config-and-retryable) + case "${STRIX_LLM:-}" in + nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5|deepseek/deepseek-v3-0324) + if [ "${LLM_API_KEY:-}" != "dummy" ]; then + echo "unexpected NVIDIA NIM key for fallback probe (${LLM_API_KEY:-})" >&2 + exit 23 + fi + echo "Error: litellm.RateLimitError: Error code: 429" + exit 1 + ;; + *) + echo "unexpected model ${STRIX_LLM:-}" >&2 + exit 24 + ;; + esac + ;; vertex-all-notfound) echo "Error: litellm.NotFoundError: Vertex_aiException - x" echo '"status": "NOT_FOUND"' @@ -5720,6 +5736,9 @@ PY if [ "$scenario" = "all-fallbacks-invalid" ]; then env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna openai_direct/gpt-5.5") fi + if [ "$scenario" = "mixed-fallback-config-and-retryable" ]; then + env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna deepseek/deepseek-v3-0324") + fi if [ "$scenario" = "same-provider-direct-openai-fallback-key" ]; then printf '%s' 'same-provider-fallback-token' >"$tmp_dir/openai_fallback_key.txt" env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.5") @@ -6500,6 +6519,17 @@ run_filtered_gate_case_if_requested() { "" \ "nvidia_nim" ;; + mixed-fallback-config-and-retryable) + run_gate_case "$STRIX_TEST_CASE_FILTER" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "" \ + "1" \ + "Configured model and fallback models were unavailable." \ + "2" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5|deepseek/deepseek-v3-0324" \ + "https://example.invalid|https://example.invalid" \ + "nvidia_nim" + ;; same-provider-direct-openai-fallback-key) run_gate_case "$STRIX_TEST_CASE_FILTER" \ "openai_direct/gpt-5.6-luna" \ @@ -12674,6 +12704,16 @@ run_gate_case "nvidia-nim-quota-openai-direct-fallback-missing-key" \ "" \ "nvidia_nim" +run_gate_case "mixed-fallback-config-and-retryable" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "" \ + "1" \ + "Configured model and fallback models were unavailable." \ + "2" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5|deepseek/deepseek-v3-0324" \ + "https://example.invalid|https://example.invalid" \ + "nvidia_nim" + run_gate_case "github-models-fallback-success-deepseek-v3" \ "vertex_ai/missing-primary" \ "github_models/deepseek/deepseek-r1-0528 github_models/deepseek/deepseek-v3-0324" \ From d376c33a3fdf013c588ab7fd30971f54f9dcee1b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:31:04 +0900 Subject: [PATCH 09/13] test(strix): exercise dedicated openai fallback routing --- scripts/ci/test_strix_quick_gate.sh | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 883a8b63a..5d5a95502 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -3469,6 +3469,34 @@ REPORT ;; esac ;; + nvidia-nim-quota-openai-direct-fallback-success) + case "${STRIX_LLM:-}" in + nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5) + if [ "${LLM_API_KEY:-}" != "dummy" ]; then + echo "unexpected primary key for NVIDIA NIM (${LLM_API_KEY:-})" >&2 + exit 25 + fi + echo "Error: litellm.RateLimitError: Error code: 429" + exit 1 + ;; + openai/gpt-5.6-luna) + if [ "${LLM_API_KEY:-}" != "dedicated-openai-fallback-token" ]; then + echo "unexpected direct-OpenAI fallback key (${LLM_API_KEY:-})" >&2 + exit 26 + fi + if [ "${LLM_API_BASE:-}" != "" ]; then + echo "unexpected direct-OpenAI fallback API base (${LLM_API_BASE:-})" >&2 + exit 27 + fi + echo "scan ok with dedicated direct-OpenAI fallback" + exit 0 + ;; + *) + echo "unexpected model ${STRIX_LLM:-}" >&2 + exit 28 + ;; + esac + ;; all-fallbacks-invalid) case "${STRIX_LLM:-}" in nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5) @@ -5733,6 +5761,11 @@ PY # then prove a later valid fallback is still attempted. env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna deepseek/deepseek-v3-0324") fi + if [ "$scenario" = "nvidia-nim-quota-openai-direct-fallback-success" ]; then + printf '%s' 'dedicated-openai-fallback-token' >"$tmp_dir/openai_fallback_key.txt" + env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna") + env_cmd+=(STRIX_OPENAI_FALLBACK_KEY_FILE="$tmp_dir/openai_fallback_key.txt") + fi if [ "$scenario" = "all-fallbacks-invalid" ]; then env_cmd+=(STRIX_FALLBACK_MODELS="openai-direct/gpt-5.6-luna openai_direct/gpt-5.5") fi @@ -6508,6 +6541,17 @@ run_filtered_gate_case_if_requested() { "" \ "nvidia_nim" ;; + nvidia-nim-quota-openai-direct-fallback-success) + run_gate_case "$STRIX_TEST_CASE_FILTER" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "" \ + "0" \ + "scan ok with dedicated direct-OpenAI fallback" \ + "2" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5|openai/gpt-5.6-luna" \ + "https://example.invalid|" \ + "nvidia_nim" + ;; all-fallbacks-invalid) run_gate_case "$STRIX_TEST_CASE_FILTER" \ "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ @@ -12704,6 +12748,16 @@ run_gate_case "nvidia-nim-quota-openai-direct-fallback-missing-key" \ "" \ "nvidia_nim" +run_gate_case "nvidia-nim-quota-openai-direct-fallback-success" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ + "" \ + "0" \ + "scan ok with dedicated direct-OpenAI fallback" \ + "2" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5|openai/gpt-5.6-luna" \ + "https://example.invalid|" \ + "nvidia_nim" + run_gate_case "mixed-fallback-config-and-retryable" \ "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" \ "" \ From cdf15180c37c3bbcb1e5fe2c48ece5ce006a6606 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:44:52 +0900 Subject: [PATCH 10/13] docs: record direct OpenAI fallback routing --- docs/adr/0002-product-technical-gap-baseline.md | 11 +++++++++++ docs/product-technical-gap-baseline.md | 1 + 2 files changed, 12 insertions(+) diff --git a/docs/adr/0002-product-technical-gap-baseline.md b/docs/adr/0002-product-technical-gap-baseline.md index 30f966c0c..274c0db8e 100644 --- a/docs/adr/0002-product-technical-gap-baseline.md +++ b/docs/adr/0002-product-technical-gap-baseline.md @@ -7,3 +7,14 @@ - Ownership: .github owns control-plane evidence; naruon and product repositories own product behavior and consumer smoke. - Figma File ID: N/A. This repository has no customer UI. A UI-owning repository must replace N/A with its real Figma File ID before a UI PR is accepted and must provide Storybook and design-token evidence. - Consequence: The document is an operational snapshot, not a merge authorization or substitute for protected GitHub review. Hourly agents must re-collect exact head SHAs, reviews, threads, and required Checks before merge. Papers/standards live in `docs/doctoring/product-technical-gap-baseline.md` and must remain consistent with this ADR. + +## Amendment: provider-specific direct-OpenAI fallback routing (2026-08-24) + +The Strix control plane must route an `openai-direct/` fallback to the +OpenAI platform API base, while retaining provider-specific credentials and +failing closed when no fallback credential or structured vulnerability report +exists. ContextualWisdomLab/.github#1295 exact head +`d376c33a3fdf013c588ab7fd30971f54f9dcee1b` records that routing and its +provider-404 regression contracts. This does not convert provider failure into +clean security evidence or authorize a merge; current-head Checks and +independent approvals remain required. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 1d884233f..201455946 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -202,6 +202,7 @@ flowchart LR - ContextualWisdomLab/.github#1265 stays GitHub CLEAN on `d4d4c2b0589065976e4bdcf5c5ae429bc21ed680` with hosted Checks green and no current-head OpenCode APPROVE after repeated `@opencode-agent` requests. CLEAN is not merge authorization. - ContextualWisdomLab/.github#1263 head `50a6ad9129b2da55d049600ecd2516ee0999d7f1` still has required Strix FAILURE on protected-main gate. - Open count is 98. No additional `.github` PR merged this pass. +- ContextualWisdomLab/.github#1295 exact head `d376c33a3fdf013c588ab7fd30971f54f9dcee1b` now binds `openai-direct/` fallbacks to the OpenAI platform endpoint and preserves fail-closed behavior for provider 404/no-report outcomes. The focused provider-routing tests pass locally; hosted Checks remain the authority. ## 5. 실행 루프와 고객의 다음 행동 From c0e15c5dbca0abad28431026ddfb348a3899e609 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 18:34:10 +0900 Subject: [PATCH 11/13] fix(opencode-review): replace nonexistent gpt-5.6-luna pool entry with gpt-5.4 The OpenCode review model pool still listed openai/gpt-5.6-luna, which does not exist upstream; a724582 established the valid gpt-5.4 contract and test_strix_quick_gate.sh asserts it for the paid-fallback ordering. --- .github/workflows/opencode-review-dispatch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index dd65d90e1..d7077277e 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -4481,7 +4481,7 @@ jobs: # cost-efficient tier, cheaper than the legacy gpt-5 it replaced # ($1/$6 vs $1.25/$10 per 1M tokens) so the org OpenAI budget # stretches further 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.6-luna 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" + 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 # step, so the pool never reached a healthy fallback model. From ecfa09d7455cfcde79b9d73313d6f70f9964ce4a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 19:50:30 +0900 Subject: [PATCH 12/13] test: align remaining gpt-5.6-luna contract expectations with valid gpt-5.4 test_opencode_agent_contract.py, test_strix_nvidia_nim_not_found_fallback.py, and test_required_workflow_queue_contract.py still pinned the nonexistent openai/gpt-5.6-luna candidate that a724582 retired from the strix workflow; align them with the shipped openai-direct/gpt-5.4 contract and refresh the paired dispatch-workflow blob SHA. --- .github/workflows/opencode-review-dispatch.yml | 2 +- tests/test_required_workflow_queue_contract.py | 2 +- tests/test_strix_nvidia_nim_not_found_fallback.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index d7077277e..dd65d90e1 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -4481,7 +4481,7 @@ jobs: # cost-efficient tier, cheaper than the legacy gpt-5 it replaced # ($1/$6 vs $1.25/$10 per 1M tokens) so the org OpenAI budget # stretches further 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" + 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.6-luna 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 # step, so the pool never reached a healthy fallback model. diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index e58f5e6c0..1d79f1daa 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -505,7 +505,7 @@ def test_nvidia_nim_defaults_preserve_existing_fallbacks_without_secret( assert strix.returncode == 0, strix.stderr assert { "provider_mode=openai_direct", - "strix_model=gpt-5.6-luna", + "strix_model=gpt-5.4", } <= set(strix_output.read_text().splitlines()) assert ( "STRIX_MODEL: ${{ steps.gate.outputs.strix_model }}" diff --git a/tests/test_strix_nvidia_nim_not_found_fallback.py b/tests/test_strix_nvidia_nim_not_found_fallback.py index 990269725..17f0e9a30 100644 --- a/tests/test_strix_nvidia_nim_not_found_fallback.py +++ b/tests/test_strix_nvidia_nim_not_found_fallback.py @@ -192,7 +192,7 @@ def test_workflow_uses_available_free_first_nvidia_plan(self) -> None: workflow = STRIX_WORKFLOW.read_text(encoding="utf-8") default_expression = ( "steps.target_visibility.outputs.is_private == 'false' && " - f"'{DEFAULT_NVIDIA_MODEL}' || 'gpt-5.6-luna'" + f"'{DEFAULT_NVIDIA_MODEL}' || 'gpt-5.4'" ) self.assertIn(default_expression, workflow) self.assertIn( @@ -202,7 +202,7 @@ def test_workflow_uses_available_free_first_nvidia_plan(self) -> None: ) self.assertIn( "steps.gate.outputs.provider_mode == 'nvidia_nim' && " - f"'{FREE_NVIDIA_FALLBACK} openai-direct/gpt-5.6-luna'", + f"'{FREE_NVIDIA_FALLBACK} openai-direct/gpt-5.4'", workflow, ) From a0c032fd2c6cd76d1ae34a6e34b08068f13b533d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 20:29:12 +0900 Subject: [PATCH 13/13] fix: restore gpt-5.4 model contract lost to worktree contamination The previous commit accidentally reverted the OPENCODE_MODEL_CANDIDATES rename and the embedded opencode.jsonc definition back to nonexistent gpt-5.6-luna by checking out origin/main's workflow file mid-verification. Re-applies both renames, refreshes the paired blob SHA, and aligns the four remaining luna-pinned assertions in test_opencode_agent_contract.py. --- .github/workflows/opencode-review-dispatch.yml | 6 +++--- tests/test_opencode_agent_contract.py | 8 ++++---- tests/test_pr_review_autofix_nvidia_nim_contract.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index dd65d90e1..33a4fd08e 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -4058,8 +4058,8 @@ jobs: "apiKey": "{env:OPENAI_API_KEY}" }, "models": { - "gpt-5.6-luna": { - "name": "OpenAI GPT-5.6 Luna (direct)", + "gpt-5.4": { + "name": "OpenAI GPT-5.4 (direct)", "tool_call": true, "reasoning": true, "options": { @@ -4481,7 +4481,7 @@ jobs: # cost-efficient tier, cheaper than the legacy gpt-5 it replaced # ($1/$6 vs $1.25/$10 per 1M tokens) so the org OpenAI budget # stretches further 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.6-luna 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" + 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 # step, so the pool never reached a healthy fallback model. diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 8ee6e86fc..2360fdb26 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -186,7 +186,7 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): ["opencode-free", "qwen3.6-plus-free"], ["opencode", "gpt-5.6-terra"], ["github-models", "deepseek/deepseek-v3-0324"], - ["openai", "gpt-5.6-luna"], + ["openai", "gpt-5.4"], ["openrouter", "deepseek/deepseek-v3.2"], ["openrouter", "qwen/qwen3-coder"], ["github-models", "openai/gpt-4.1"], @@ -197,7 +197,7 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): ["github-models", "deepseek/deepseek-r1"], ] assert zen_models == ["gpt-5.6-terra"] - assert direct_openai_models == ["gpt-5.6-luna"] + assert direct_openai_models == ["gpt-5.4"] assert openrouter_models == [ "deepseek/deepseek-v3.2", "qwen/qwen3-coder", @@ -1740,7 +1740,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert ( "opencode/gpt-5.6-terra " "github-models/deepseek/deepseek-v3-0324 " - "openai/gpt-5.6-luna " + "openai/gpt-5.4 " "openrouter/deepseek/deepseek-v3.2 " "openrouter/qwen/qwen3-coder " "github-models/openai/gpt-4.1 " @@ -1887,7 +1887,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): ) assert ( "github-models/deepseek/deepseek-v3-0324 " - "openai/gpt-5.6-luna " + "openai/gpt-5.4 " "openrouter/deepseek/deepseek-v3.2 " "openrouter/qwen/qwen3-coder " "github-models/openai/gpt-4.1 " diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 799b9e9fb..3b6c3a054 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -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 = "dd65d90e10e5040562b501ade1a40f89572f0984" +REVIEW_DISPATCH_BLOB_SHA = "33a4fd08e93e653e715b380ad9f9523e11d231e6" def _workflow_text(path: Path) -> str: