Merge pull request 'fix(e2e): claude-code MiniMax slug must be bare MiniMax-M2.7 not colon (internal#718 UNREGISTERED)' (#2311) from fix/e2e-claude-code-minimax-bare-slug into main
ci-arm64-advisory / fast-checks (push) Waiting to run
Block internal-flavored paths / Block forbidden paths (push) Successful in 3s
CI / Detect changes (push) Successful in 7s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Successful in 2s
E2E API Smoke Test / detect-changes (push) Successful in 8s
E2E Chat / detect-changes (push) Successful in 10s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 9s
Handlers Postgres Integration / detect-changes (push) Successful in 5s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Successful in 4s
CI / Platform (Go) (push) Successful in 2s
CI / Canvas (Next.js) (push) Successful in 1s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (push) Successful in 6s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 7s
E2E Chat / E2E Chat (push) Successful in 4s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Successful in 2s
CI / Canvas Deploy Status (push) Successful in 2s
CI / Python Lint & Test (push) Successful in 28s
CI / Shellcheck (E2E scripts) (push) Successful in 16s
CI / all-required (push) Successful in 4s
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 1m0s
Handlers Postgres Integration / Handlers Postgres Integration (push) Successful in 1m8s
E2E Staging SaaS (full lifecycle) / pr-validate (push) Successful in 39s
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (push) Failing after 2m15s
publish-workspace-server-image / build-and-push (push) Successful in 3m27s
publish-workspace-server-image / Production auto-deploy (push) Successful in 2m46s
E2E Staging SaaS (full lifecycle) / E2E Staging Platform Boot (push) Failing after 6m15s
E2E Staging Reconciler (heals terminated EC2) / pr-validate (push) Successful in 4s
E2E Staging Reconciler (heals terminated EC2) / E2E Staging Reconciler (push) Failing after 17m11s

This commit was merged in pull request #2311.
This commit is contained in:
2026-06-05 17:19:03 +00:00
4 changed files with 68 additions and 45 deletions
+31 -18
View File
@@ -11,10 +11,10 @@
# default + 401, see PR #1714.)
#
# claude-code → auth-aware:
# E2E_MINIMAX_API_KEY → "minimax:MiniMax-M2.7"
# (colon-namespaced BYOK id; bare
# "MiniMax-M2" 400s on a deploy-skewed
# staging registry — #2263)
# E2E_MINIMAX_API_KEY → "MiniMax-M2.7"
# (BARE registered BYOK id — see the
# claude-code dispatch arm below for
# why bare, not the colon form)
# E2E_ANTHROPIC_API_KEY → "claude-sonnet-4-6"
# otherwise → "sonnet"
#
@@ -88,24 +88,37 @@ pick_model_slug() {
# (it is absent from manifest.json + runtime_registry.go). Its config.yaml
# declares `runtime: claude-code` and copies the claude-code `providers:`
# block (providers.yaml:21 "The same block is copy-pasted into the seo-agent
# template"), so its model dispatch is IDENTICAL to claude-code's: the
# MiniMax BYOK colon id (the staging-default key path), else direct
# template"), so its model dispatch is IDENTICAL to claude-code's: the BARE
# registered MiniMax BYOK id (the staging-default key path), else direct
# Anthropic, else the OAuth `sonnet` alias. Sharing the claude-code branch
# keeps the SSOT one place — a seo-agent run is just a claude-code run
# behind a productized template skin.
# behind a productized template skin, and (because the runtime resolves to
# claude-code server-side) its model must be a *claude-code-registered* form.
claude-code|seo-agent)
if [ -n "${E2E_MINIMAX_API_KEY:-}" ]; then
# Namespaced (colon) BYOK id, not bare "MiniMax-M2" (#2263 deploy-skew):
# bare ids can lag the deployed staging ws-server's compiled registry,
# so workspace-create's validateRegisteredModelForRuntime 400s the bare
# form on an older image. The colon-namespaced `minimax:MiniMax-M2.7`
# resolves the same way the proven-working sibling `moonshot/kimi-k2.6`
# does. It stays in the BYOK `minimax` arm (providers.yaml:851), so
# DeriveProvider -> provider_selection=minimax (BYOK) and the #1994
# byok-not-platform guard (test_staging_full_saas.sh:1000) still passes —
# unlike the slash/platform form `minimax/MiniMax-M2.7`, which resolves
# to provider=platform and would trip that guard.
printf 'minimax:MiniMax-M2.7'
# BARE registered BYOK id `MiniMax-M2.7`, NOT the colon form
# `minimax:MiniMax-M2.7`. On the claude-code runtime the three MiniMax
# spellings have three DISTINCT, intentional outcomes (provider-registry
# SSOT, internal#718; pinned by workspace-server/internal/providers/
# derive_provider_matrix_test.go, the #2263/#2274 "colon-vs-slash-vs-bare
# triple"):
# * bare "MiniMax-M2.7" -> provider=minimax (BYOK, MINIMAX_API_KEY)
# * slash "minimax/MiniMax-M2.7" -> provider=platform (CP proxy bills)
# * colon "minimax:MiniMax-M2.7" -> UNREGISTERED 422 (the claude-code
# adapter CANNOT strip the `minimax:` prefix, so the id is not a
# registered model for runtime claude-code; create-validation,
# internal#718, rejects it)
# The bare form is registered in the claude-code `minimax` arm
# (registry_gen.go:88 Models=[MiniMax-M2,MiniMax-M2.7,
# MiniMax-M2.7-highspeed,MiniMax-M3]) and derives provider=minimax (BYOK
# via MINIMAX_API_KEY), so it satisfies the #1994 byok-not-platform guard
# (test_staging_full_saas.sh) AND passes create-validation — unlike the
# colon form, which 422'd "5/11 Provisioning parent workspace" with
# UNREGISTERED_MODEL_FOR_RUNTIME on real staging (job 295075).
# NOTE: the colon form IS the correct BYOK-minimax id on openclaw/hermes
# (those adapters DO strip `minimax:` — matrix test), but this dispatch
# arm only emits for claude-code/seo-agent, where bare is the right form.
printf 'MiniMax-M2.7'
elif [ -n "${E2E_ANTHROPIC_API_KEY:-}" ]; then
printf 'claude-sonnet-4-6'
else
+8 -3
View File
@@ -48,14 +48,19 @@ run_test "hermes → slash-form (derive-provider.sh contract)" hermes
run_test "codex → slash-form fallback" codex "openai/gpt-4o"
run_test "claude-code → OAuth/default alias" claude-code "sonnet"
# BARE registered BYOK id (registry_gen.go:88), NOT colon `minimax:…`. On
# claude-code the colon form is intentionally UNREGISTERED (the adapter can't
# strip `minimax:`) and 422s create-validation (internal#718, job 295075);
# bare resolves to provider=minimax BYOK. Pinned by the matrix test's
# colon-vs-slash-vs-bare triple in derive_provider_matrix_test.go.
got=$(unset E2E_MODEL_SLUG E2E_ANTHROPIC_API_KEY; E2E_MINIMAX_API_KEY="mx-test" pick_model_slug claude-code)
assert_eq "claude-code + MiniMax key → MiniMax model" "$got" "minimax:MiniMax-M2.7"
assert_eq "claude-code + MiniMax key → bare registered MiniMax model" "$got" "MiniMax-M2.7"
got=$(unset E2E_MODEL_SLUG E2E_MINIMAX_API_KEY; E2E_ANTHROPIC_API_KEY="sk-ant-test" pick_model_slug claude-code)
assert_eq "claude-code + Anthropic API key → Anthropic API model" "$got" "claude-sonnet-4-6"
got=$(unset E2E_MODEL_SLUG; E2E_MINIMAX_API_KEY="mx-priority" E2E_ANTHROPIC_API_KEY="sk-ant-loser" pick_model_slug claude-code)
assert_eq "claude-code + both keys → MiniMax priority" "$got" "minimax:MiniMax-M2.7"
assert_eq "claude-code + both keys → MiniMax priority (bare)" "$got" "MiniMax-M2.7"
# ── seo-agent (claude-code-adapter template variant) ──
# seo-agent shares the claude-code dispatch branch (it reuses the claude-code
@@ -65,7 +70,7 @@ assert_eq "claude-code + both keys → MiniMax priority" "$got" "mini
run_test "seo-agent → claude-code default alias" seo-agent "sonnet"
got=$(unset E2E_MODEL_SLUG E2E_ANTHROPIC_API_KEY; E2E_MINIMAX_API_KEY="mx-test" pick_model_slug seo-agent)
assert_eq "seo-agent + MiniMax key → MiniMax model (==claude-code)" "$got" "minimax:MiniMax-M2.7"
assert_eq "seo-agent + MiniMax key → bare MiniMax model (==claude-code)" "$got" "MiniMax-M2.7"
got=$(unset E2E_MODEL_SLUG E2E_MINIMAX_API_KEY; E2E_ANTHROPIC_API_KEY="sk-ant-test" pick_model_slug seo-agent)
assert_eq "seo-agent + Anthropic key → Anthropic model (==claude-code)" "$got" "claude-sonnet-4-6"
+28 -23
View File
@@ -641,16 +641,19 @@ for r in (d.get("workspaces") or d.get("results") or []):
####################################################################
# NOTE: this is now a BEST-EFFORT arm, not the REQUIRE-LIVE backbone.
# mock (run_mock above) is the guaranteed, no-key validation that keeps
# the gate honest. MiniMax-create is fragile in CI: the namespaced model
# id minimax:MiniMax-M2.7 is NOT in claude-code's native model set and
# does NOT resolve via DeriveProvider (its only prefix-owner, byok-minimax,
# is not wired as a claude-code runtime arm), so the create is rejected
# 422 UNREGISTERED_MODEL_FOR_RUNTIME before any provisioning (RCA core
# registry_gen.go Runtimes["claude-code"]). Rather than red the REQUIRED
# gate on that registry-skew (or on any transient MiniMax provisioning /
# model-registration issue), this arm reports a best-effort MISS via
# bestfail() and lets mock carry the validation. If MiniMax DOES come up
# it validates as a bonus real-LLM check.
# the gate honest. This arm uses the BARE registered BYOK id `MiniMax-M2.7`
# (NOT the colon `minimax:MiniMax-M2.7`): on claude-code the colon form is
# INTENTIONALLY unregistered — the claude-code adapter cannot strip the
# `minimax:` prefix, so DeriveProvider rejects it 422
# UNREGISTERED_MODEL_FOR_RUNTIME before any provisioning (provider-registry
# SSOT, internal#718; pinned by derive_provider_matrix_test.go's
# colon-vs-slash-vs-bare triple, and observed on real staging job 295075).
# The bare id is in claude-code's `minimax` arm (registry_gen.go:88
# Models=[MiniMax-M2,MiniMax-M2.7,MiniMax-M2.7-highspeed,MiniMax-M3]) and
# derives provider=minimax (BYOK via MINIMAX_API_KEY), so create-validation
# accepts it. This arm stays BEST-EFFORT (bestfail, non-gating) for transient
# MiniMax provisioning / backend issues — mock carries the REQUIRED gate; if
# MiniMax DOES come up it validates as a bonus real-LLM check.
# Drives the claude-code runtime against MiniMax (BYOK) using the
# already-present Gitea secret MOLECULE_STAGING_MINIMAX_API_KEY,
# surfaced into the env as E2E_MINIMAX_API_KEY (same name + secret the
@@ -663,12 +666,12 @@ for r in (d.get("workspaces") or d.get("results") or []):
# and routes ANTHROPIC_BASE_URL → api.minimax.io/anthropic. So the
# ONLY tenant secret needed is {"MINIMAX_API_KEY": <key>} — exactly
# the SECRETS_JSON branch test_staging_full_saas.sh uses.
# - Model id is the NAMESPACED colon-form `minimax:MiniMax-M2.7`, the
# registered BYOK arm for claude-code (registry_gen.go Runtimes
# ["claude-code"]["minimax"]). Per core#2263 the BARE `MiniMax-M2`
# id can 400 on a registry-skewed ws-server build; the namespaced
# form resolves the way kimi's `moonshot/…` does, so it's the
# robust choice for the gate.
# - Model id is the BARE `MiniMax-M2.7`, the registered BYOK arm for
# claude-code (registry_gen.go:88 Runtimes["claude-code"]["minimax"]
# Models). DeriveProvider routes bare → provider=minimax (BYOK). The
# colon-namespaced `minimax:MiniMax-M2.7` is UNREGISTERED on claude-code
# (the adapter can't strip `minimax:`; internal#718) and 422s create —
# it is only the correct BYOK id on openclaw/hermes, which DO strip it.
run_minimax() {
echo ""
echo "=== minimax (claude-code BYOK) happy path ==="
@@ -685,16 +688,18 @@ import json, os
print(json.dumps({'MINIMAX_API_KEY': os.environ['E2E_MINIMAX_API_KEY']}))
")
local resp wsid
# Namespaced BYOK model id (core#2263): bare MiniMax-M2 can 400 on a
# registry-skewed ws-server build; minimax:MiniMax-M2.7 is the
# registered claude-code BYOK arm and resolves like kimi's moonshot/…
# BARE registered BYOK model id `MiniMax-M2.7` (registry_gen.go:88). The
# colon form `minimax:MiniMax-M2.7` is UNREGISTERED on claude-code (adapter
# can't strip `minimax:`; internal#718) and 422s create — bare derives
# provider=minimax (BYOK via MINIMAX_API_KEY) and passes create-validation.
resp=$(curl -s -X POST "$BASE/workspaces" ${ADMIN_AUTH[@]+"${ADMIN_AUTH[@]}"} -H "Content-Type: application/json" \
-d "{\"name\":\"Priority E2E (minimax)\",\"runtime\":\"claude-code\",\"model\":\"minimax:MiniMax-M2.7\",\"tier\":1,\"secrets\":$secrets}")
-d "{\"name\":\"Priority E2E (minimax)\",\"runtime\":\"claude-code\",\"model\":\"MiniMax-M2.7\",\"tier\":1,\"secrets\":$secrets}")
wsid=$(echo "$resp" | python3 -c 'import json,sys;print(json.load(sys.stdin).get("id",""))') || true
if [ -z "$wsid" ]; then
# BEST-EFFORT: MiniMax-create is fragile (see header — the namespaced
# model id is registry-skewed → 422). Do NOT red the gate; mock is the
# required backbone. Report the create response so the skew is visible.
# BEST-EFFORT: real MiniMax create/provision can still miss on transient
# backend / provisioning issues (the bare model id itself is registered —
# see header). Do NOT red the gate; mock is the required backbone. Report
# the create response so any miss is visible.
bestfail "create minimax workspace (best-effort; mock carries the gate)" "$resp"
return 0
fi
+1 -1
View File
@@ -1095,7 +1095,7 @@ fi
# identical on main's scheduled synthetic E2E and on PRs (so it is an
# environmental backend regression, never PR-introduced).
if echo "$AGENT_TEXT" | grep -qiF "message contained no text content"; then
fail "A2A — EMPTY COMPLETION (backend regression, NOT a platform/workspace-server bug). The configured model (MODEL_SLUG=${MODEL_SLUG:-?}) returned a 2xx completion with no text part; the runtime surfaced 'message contained no text content.'. Operator action: check the staging LLM backend / proxy for the canary model (the claude-code default is minimax:MiniMax-M2.7 since #2263; was bare MiniMax-M2 #2710) — empty assistant turns, not an auth/quota/boot fault. Raw: $AGENT_TEXT"
fail "A2A — EMPTY COMPLETION (backend regression, NOT a platform/workspace-server bug). The configured model (MODEL_SLUG=${MODEL_SLUG:-?}) returned a 2xx completion with no text part; the runtime surfaced 'message contained no text content.'. Operator action: check the staging LLM backend / proxy for the canary model (the claude-code MiniMax-BYOK default is the BARE registered id MiniMax-M2.7 — the colon minimax:MiniMax-M2.7 is UNREGISTERED on claude-code, internal#718) — empty assistant turns, not an auth/quota/boot fault. Raw: $AGENT_TEXT"
fi
# Generic catch-all — falls through if none of the known regressions hit.
if echo "$AGENT_TEXT" | grep -qiE "error|exception"; then