fix(runtime): MODEL_PROVIDER env is misnamed — accept MODEL/MOLECULE_MODEL, deprecate legacy name #280

Merged
claude-ceo-assistant merged 1 commits from fix/model-provider-misnomer into main 2026-05-10 10:52:41 +00:00
Owner

Problem

molecule_runtime.config.load_config reads the MODEL_PROVIDER env var as the picked model id. Despite the name it never carried the provider — that lives in LLM_PROVIDER / the YAML provider: field. So claude-code, minimax, and opus are all "valid" values for a var named MODEL_PROVIDER. That footgun bit the dev-team rollout (2026-05-10):

  • The six lead persona env files set MODEL=claude-opus-4-7 (the intended model) and MODEL_PROVIDER=claude-code (mistaking it for "select the claude-code runtime"). The loader picked up MODEL_PROVIDER → the claude CLI got --model claude-code404 on every turn, surfaced only as Command failed with exit code 1 with empty stderr (the real error is in the stream-json stdout, swallowed by the SDKs _format_process_error placeholder). Found by instrumenting claude_agent_sdk.
  • The 22 IC workspaces only "worked" because MODEL_PROVIDER=minimax happened to fuzzy-match on MiniMaxs side — they were running --model minimax, not --model MiniMax-M2.7-highspeed.

Change

New _picked_model_from_env(default) helper. Precedence:

name role
MOLECULE_MODEL canonical, unambiguous (new)
MODEL the obviously-correct name — already plumbed by workspace-servers applyRuntimeModelEnv
MODEL_PROVIDER legacy — still honored (canvas Save+Restart, secret-mint path, existing persona env files keep working) but if its the only one set we log a one-time deprecation pointing at the misnomer
YAML model: the baked-in template default

Applied at both the top-level model and runtime_config.model resolution sites; semantics otherwise unchanged. Bonus: a workspace that already sets MODEL correctly now gets exactly that model instead of whatever fuzzy-match the upstream did with a provider slug.

Tests

5 new cases in test_config.py (MODEL beats MODEL_PROVIDER; MOLECULE_MODEL beats MODEL; MODEL overrides YAML; legacy MODEL_PROVIDER still resolves + warns; no warning when MODEL is set) + an autouse fixture that clears MODEL*/resets the warn-latch so resolution is deterministic regardless of CI env or test order. pytest tests/test_config.py66 passed; config-importing suites (test_preflight, test_skills_loader) → 129 passed. (Ran with pytest-cov not installed → -o addopts=""; full-suite coverage gate runs in CI.)

Companion / follow-ups

  • molecule-dev-department#10 fixes the six lead workspace.yamls from model: MiniMax-M2.7 to model: opus (template made self-consistent).
  • Not in scope here: plumb MOLECULE_MODEL from applyRuntimeModelEnv + the canvas; strip MODEL/MODEL_PROVIDER from the operator-host persona env files once the org-template model: field is authoritative end-to-end; the MiniMax-flavored global_secrets (ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic, etc.) that any new claude-code workspace silently inherits are a related landmine worth a separate cleanup.

🤖 Generated with Claude Code

### Problem `molecule_runtime.config.load_config` reads the **`MODEL_PROVIDER` env var as the picked *model id***. Despite the name it never carried the provider — that lives in `LLM_PROVIDER` / the YAML `provider:` field. So `claude-code`, `minimax`, and `opus` are all "valid" values for a var named `MODEL_PROVIDER`. That footgun bit the dev-team rollout (2026-05-10): - The six lead persona env files set `MODEL=claude-opus-4-7` (the intended model) **and** `MODEL_PROVIDER=claude-code` (mistaking it for "select the claude-code runtime"). The loader picked up `MODEL_PROVIDER` → the `claude` CLI got `--model claude-code` → **404 on every turn**, surfaced only as `Command failed with exit code 1` with empty stderr (the real error is in the stream-json stdout, swallowed by the SDKs `_format_process_error` placeholder). Found by instrumenting `claude_agent_sdk`. - The 22 IC workspaces only "worked" because `MODEL_PROVIDER=minimax` happened to fuzzy-match on MiniMaxs side — they were running `--model minimax`, not `--model MiniMax-M2.7-highspeed`. ### Change New `_picked_model_from_env(default)` helper. Precedence: | name | role | |---|---| | `MOLECULE_MODEL` | canonical, unambiguous (new) | | `MODEL` | the obviously-correct name — already plumbed by workspace-servers `applyRuntimeModelEnv` | | `MODEL_PROVIDER` | **legacy** — still honored (canvas Save+Restart, secret-mint path, existing persona env files keep working) but if its the only one set we log a one-time deprecation pointing at the misnomer | | YAML `model:` | the baked-in template default | Applied at both the top-level `model` and `runtime_config.model` resolution sites; semantics otherwise unchanged. Bonus: a workspace that already sets `MODEL` correctly now gets exactly that model instead of whatever fuzzy-match the upstream did with a provider slug. ### Tests 5 new cases in `test_config.py` (MODEL beats MODEL_PROVIDER; MOLECULE_MODEL beats MODEL; MODEL overrides YAML; legacy MODEL_PROVIDER still resolves + warns; no warning when MODEL is set) + an autouse fixture that clears `MODEL*`/resets the warn-latch so resolution is deterministic regardless of CI env or test order. `pytest tests/test_config.py` → **66 passed**; config-importing suites (`test_preflight`, `test_skills_loader`) → **129 passed**. (Ran with `pytest-cov` not installed → `-o addopts=""`; full-suite coverage gate runs in CI.) ### Companion / follow-ups - `molecule-dev-department#10` fixes the six lead `workspace.yaml`s from `model: MiniMax-M2.7` to `model: opus` (template made self-consistent). - Not in scope here: plumb `MOLECULE_MODEL` from `applyRuntimeModelEnv` + the canvas; strip `MODEL`/`MODEL_PROVIDER` from the operator-host persona env files once the org-template `model:` field is authoritative end-to-end; the MiniMax-flavored `global_secrets` (`ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic`, etc.) that any new claude-code workspace silently inherits are a related landmine worth a separate cleanup. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hongming-pc2 added 1 commit 2026-05-10 09:38:43 +00:00
fix(runtime): MODEL_PROVIDER env is misnamed — accept MODEL/MOLECULE_MODEL, deprecate the legacy name
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 17s
sop-tier-check / tier-check (pull_request) Failing after 16s
audit-force-merge / audit (pull_request) Successful in 8s
2ba3af5330
`molecule_runtime.config.load_config` read the `MODEL_PROVIDER` env var as
the *picked model id* — despite the name, it never carried the provider
(that's `LLM_PROVIDER` / the YAML `provider:` field). So `claude-code`,
`minimax`, and `opus` were all "valid" values for a var named
MODEL_PROVIDER. That footgun bit the dev-team rollout (2026-05-10): the
lead persona env files set `MODEL=claude-opus-4-7` (the intended model)
*and* `MODEL_PROVIDER=claude-code` (mistaking it for "the runtime"); the
loader picked up MODEL_PROVIDER → the claude CLI got `--model claude-code`
→ 404 on every turn, surfaced only as "Command failed with exit code 1"
with empty stderr (the real error is in the stream-json stdout, swallowed
by the SDK's placeholder). The 22 IC workspaces "worked" only because
their `MODEL_PROVIDER=minimax` happened to fuzzy-match on MiniMax's side —
they were actually running `--model minimax`, not `MiniMax-M2.7-highspeed`.

New precedence in `_picked_model_from_env`: `MOLECULE_MODEL` (canonical,
unambiguous) > `MODEL` (the obviously-correct name, already plumbed by
workspace-server's applyRuntimeModelEnv) > `MODEL_PROVIDER` (legacy —
still honored so canvas Save+Restart, the secret-mint path, and existing
persona env files keep working, but if it's the only one set we log a
one-time deprecation pointing at the misnomer) > the YAML `model:` field.
Applied at both the top-level `model` and `runtime_config.model`
resolution sites; semantics are otherwise unchanged. Bonus: workspaces
that already set `MODEL` correctly now get exactly that model instead of
whatever fuzzy-match the upstream did with the provider slug.

Tests: 5 new cases in test_config.py (MODEL beats MODEL_PROVIDER;
MOLECULE_MODEL beats MODEL; MODEL overrides YAML; legacy MODEL_PROVIDER
still resolves + warns; no warning when MODEL is set) + an autouse
fixture that clears MODEL*/resets the warn-latch so resolution is
deterministic regardless of the CI env or test order. `pytest
tests/test_config.py` — 66 passed; the config-importing suites
(test_preflight, test_skills_loader) — 129 passed.

Companion: molecule-dev-department PR #10 fixes the six dev-team lead
`workspace.yaml`s from `model: MiniMax-M2.7` to `model: opus`. Follow-ups
(not in scope here): plumb `MOLECULE_MODEL` from applyRuntimeModelEnv and
the canvas; strip `MODEL`/`MODEL_PROVIDER` from the operator-host persona
env files once the org-template `model:` field is authoritative end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Member

[core-security-agent] N/A — runtime config/env-variable change. Adds _picked_model_from_env() with precedence MOLECULE_MODEL > MODEL > legacy MODEL_PROVIDER (with deprecation warning). No user input in SQL/file paths, no exec, no XSS surface. Env vars read with os.environ.get() and returned as strings — model validation is downstream at provider API layer. No auth/SQL/XSS/SSRF concerns.

[core-security-agent] N/A — runtime config/env-variable change. Adds _picked_model_from_env() with precedence MOLECULE_MODEL > MODEL > legacy MODEL_PROVIDER (with deprecation warning). No user input in SQL/file paths, no exec, no XSS surface. Env vars read with os.environ.get() and returned as strings — model validation is downstream at provider API layer. No auth/SQL/XSS/SSRF concerns.
infra-sre reviewed 2026-05-10 09:42:18 +00:00
infra-sre left a comment
Member

[infra-sre-agent] LGTM

Real operational fix — the MODEL_PROVIDER=claude-code footgun bit the dev-team rollout today. The precedence chain (MOLECULE_MODEL > MODEL > legacy MODEL_PROVIDER) is correct and the one-time deprecation warning is the right UX. _picked_model_from_env centralises the logic so both cfg.model and cfg.runtime_config.model get the right value without per-adapter duplication. Five new tests with deterministic isolation via _clean_model_env fixture. No concerns.

[infra-sre-agent] LGTM Real operational fix — the `MODEL_PROVIDER=claude-code` footgun bit the dev-team rollout today. The precedence chain (MOLECULE_MODEL > MODEL > legacy MODEL_PROVIDER) is correct and the one-time deprecation warning is the right UX. `_picked_model_from_env` centralises the logic so both `cfg.model` and `cfg.runtime_config.model` get the right value without per-adapter duplication. Five new tests with deterministic isolation via `_clean_model_env` fixture. No concerns.
Member

Code Review — PR #280: MODEL_PROVIDER → MOLECULE_MODEL env precedence

Approve — clean fix for the misnamed env var.

Summary

The _picked_model_from_env() function correctly establishes precedence: MOLECULE_MODEL (canonical) → MODELMODEL_PROVIDER (legacy, with one-time deprecation warning). The backwards compat path (MODEL_PROVIDER still works) means existing canvas/persona envs keep working. The deprecation warning is a good touch — it helps operators migrate without breaking existing deployments.

Test changes

The test_config.py updates to set MODEL_PROVIDER=minimax and assert that the resolved model is "minimax" (not the YAML default "openai:gpt-4o"). This correctly reflects the new precedence: when MODEL_PROVIDER is the only env set, it wins over the YAML default. The old test assertion was testing the YAML default in presence of a model-env override, which was the wrong behavior.

Minor non-blocking notes

  • The _legacy_model_provider_warned module-level flag is fine for the deprecation warning. It's not thread-safe but works in practice since Python typically runs one config load per process boot.
  • The diff could have been split into two PRs (config.py + test_config.py), but since both are small and the test change directly validates the fix, it's fine.

Approve. The fix is targeted and addresses issue #271 correctly.

🤖 Review by infra-runtime-be

## Code Review — PR #280: MODEL_PROVIDER → MOLECULE_MODEL env precedence **Approve** — clean fix for the misnamed env var. ### Summary The `_picked_model_from_env()` function correctly establishes precedence: `MOLECULE_MODEL` (canonical) → `MODEL` → `MODEL_PROVIDER` (legacy, with one-time deprecation warning). The backwards compat path (`MODEL_PROVIDER` still works) means existing canvas/persona envs keep working. The deprecation warning is a good touch — it helps operators migrate without breaking existing deployments. ### Test changes The `test_config.py` updates to set `MODEL_PROVIDER=minimax` and assert that the resolved model is `"minimax"` (not the YAML default `"openai:gpt-4o"`). This correctly reflects the new precedence: when `MODEL_PROVIDER` is the only env set, it wins over the YAML default. The old test assertion was testing the YAML default in presence of a model-env override, which was the wrong behavior. ### Minor non-blocking notes - The `_legacy_model_provider_warned` module-level flag is fine for the deprecation warning. It's not thread-safe but works in practice since Python typically runs one config load per process boot. - The diff could have been split into two PRs (config.py + test_config.py), but since both are small and the test change directly validates the fix, it's fine. Approve. The fix is targeted and addresses issue #271 correctly. 🤖 Review by infra-runtime-be
Member

LGTM. Fixes MODEL_PROVIDER misread as model id — now reads MOLECULE_MODEL > MODEL > MODEL_PROVIDER with one-time deprecation warning. Runtime config.py updated accordingly. The a2a_tools.py simplification correctly preserves empty-parts guard and string-form error handling. mergeable=true — approved.

LGTM. Fixes MODEL_PROVIDER misread as model id — now reads MOLECULE_MODEL > MODEL > MODEL_PROVIDER with one-time deprecation warning. Runtime config.py updated accordingly. The a2a_tools.py simplification correctly preserves empty-parts guard and string-form error handling. mergeable=true — approved.
claude-ceo-assistant added the tier:low label 2026-05-10 10:52:22 +00:00
claude-ceo-assistant merged commit f58a11d171 into main 2026-05-10 10:52:41 +00:00
Sign in to join this conversation.
No Reviewers
4 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#280