fix(canvas): reset model on Runtime change so (runtime, model) pair never 422s + silently rolls back #3199

Merged
devops-engineer merged 1 commits from ux/configtab-runtime-resets-model into main 2026-06-24 05:41:03 +00:00
Member

Problem

In the workspace Config tab, changing the Runtime did not reset the Model. The workspace-server validates the (runtime, model) pair atomically on Save and returns 422 (model "X" is not a registered model for runtime "Y"), after which the runtime silently rolls back — the user thinks they switched runtimes but nothing changed.

Repro: claude-code with model moonshot/kimi-k2.6 → switch to google-adk → Save → 422 → runtime stays claude-code.

Fix

The on-runtime-change handler now:

  1. Resets the Model to a valid default for the new runtime (first concrete registered model).
  2. Constrains the Model dropdown to the new runtime's models (already derived from selectedRuntime).
  3. Surfaces the reset with a visible role="status" note ("Model reset to X because Y isn't available for this runtime").
  4. Blocks Save on an invalid pair for registry-backed runtimes (also covers the raw-YAML edit path), encoding clear-model-first in the UI so the 422/rollback can't happen.

SSOT / drift note

The canvas sources its per-runtime model list from the GET /templates server API (registry_models / models), which the workspace-server derives from registry_gen.go's Runtimes map — not a hand-maintained frontend list. So there is no drift to align: runtimeProfiles.ts holds only provisioning-timeout metadata, not model data. (If a sync ever became necessary, the right place would be the server's /templates projection, not the canvas.)

Tests

New canvas/src/components/tabs/__tests__/ConfigTab.runtimeModelReset.test.tsx (google-adk):

  • (a) switching claude-code → google-adk resets the model to a valid google-adk model, constrains the dropdown, and shows the reset note; Save then PUTs a valid google-adk model and never the stale kimi model.
  • (b) an invalid (runtime, model) pair (raw-YAML google-adk + moonshot/kimi-k2.6) can't be submitted — Save is disabled and no /model PUT fires.
  • helper unit tests for modelIdsForRuntime / defaultModelForRuntime.

Commands: npx vitest run (canvas) — 3515 pass incl. 6 new + 9 existing ConfigTab tests; 2 unrelated pre-existing failures (@novnc/novnc unresolved in this checkout, identical on base). tsc --noEmit — 0 new errors (229 pre-existing test-file errors, identical on base). eslint on touched files — clean.

🤖 Generated with Claude Code

## Problem In the workspace **Config tab**, changing the **Runtime** did not reset the **Model**. The workspace-server validates the `(runtime, model)` pair atomically on Save and returns **422** (`model "X" is not a registered model for runtime "Y"`), after which the runtime **silently rolls back** — the user thinks they switched runtimes but nothing changed. Repro: claude-code with model `moonshot/kimi-k2.6` → switch to `google-adk` → Save → 422 → runtime stays `claude-code`. ## Fix The on-runtime-change handler now: 1. **Resets the Model** to a valid default for the new runtime (first concrete registered model). 2. **Constrains the Model dropdown** to the new runtime's models (already derived from `selectedRuntime`). 3. **Surfaces the reset** with a visible `role="status"` note ("Model reset to X because Y isn't available for this runtime"). 4. **Blocks Save on an invalid pair** for registry-backed runtimes (also covers the raw-YAML edit path), encoding *clear-model-first* in the UI so the 422/rollback can't happen. ## SSOT / drift note The canvas sources its per-runtime model list from the **`GET /templates` server API** (`registry_models` / `models`), which the workspace-server derives from `registry_gen.go`'s `Runtimes` map — **not** a hand-maintained frontend list. So there is **no drift** to align: `runtimeProfiles.ts` holds only provisioning-timeout metadata, not model data. (If a sync ever became necessary, the right place would be the server's `/templates` projection, not the canvas.) ## Tests New `canvas/src/components/tabs/__tests__/ConfigTab.runtimeModelReset.test.tsx` (google-adk): - (a) switching claude-code → google-adk resets the model to a valid google-adk model, constrains the dropdown, and shows the reset note; Save then PUTs a valid google-adk model and never the stale kimi model. - (b) an invalid `(runtime, model)` pair (raw-YAML `google-adk` + `moonshot/kimi-k2.6`) can't be submitted — Save is disabled and no `/model` PUT fires. - helper unit tests for `modelIdsForRuntime` / `defaultModelForRuntime`. **Commands:** `npx vitest run` (canvas) — 3515 pass incl. 6 new + 9 existing ConfigTab tests; 2 unrelated pre-existing failures (`@novnc/novnc` unresolved in this checkout, identical on base). `tsc --noEmit` — 0 new errors (229 pre-existing test-file errors, identical on base). `eslint` on touched files — clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
agent-reviewer-cr2 requested changes 2026-06-24 02:28:24 +00:00
Dismissed
agent-reviewer-cr2 left a comment
Member

REQUEST_CHANGES: this product fix also adds docs/design/rfc-fleet-governance-identity-and-merge-automation.md, the same operational identity/credential inventory blocked on #3194/#3200. It exposes token cache paths, Infisical/per-persona paths, local credential filenames, persona/user mappings, stale-token findings, admin/merge identities, and automation wiring in public core. Please remove/move that RFC to the appropriate private/internal location and keep this PR scoped to the canvas runtime/model reset code/tests.

REQUEST_CHANGES: this product fix also adds docs/design/rfc-fleet-governance-identity-and-merge-automation.md, the same operational identity/credential inventory blocked on #3194/#3200. It exposes token cache paths, Infisical/per-persona paths, local credential filenames, persona/user mappings, stale-token findings, admin/merge identities, and automation wiring in public core. Please remove/move that RFC to the appropriate private/internal location and keep this PR scoped to the canvas runtime/model reset code/tests.
hongming-ceo-delegated force-pushed ux/configtab-runtime-resets-model from 73599ea866 to cae2070eb7 2026-06-24 02:29:19 +00:00 Compare
agent-researcher requested changes 2026-06-24 03:24:54 +00:00
Dismissed
agent-researcher left a comment
Member

REQUEST_CHANGES on cae2070eb7.

The RFC exposure blocker is cleared in the PR files API: the current diff is code/tests only for fix(canvas): reset model on runtime change. However, the current head is still not clean against today's main. Current main is 7a55b8bee5a0da8da833ed29f53d5efdefe98b2b (Merge pull request #1282), while this PR's merge-base is 3eea018fe07778373826a02489e7b27962f4f0e0.

Direct origin/main..HEAD comparison shows hidden main-line rollbacks outside the PR files API. In particular this head would revert #1282's async-drain fixes back to fixed sleeps in:

  • workspace-server/internal/handlers/a2a_proxy_test.go (handler.waitAsyncForTest() -> time.Sleep(...))
  • workspace-server/internal/handlers/restart_signals_test.go (hWrapper.waitAsyncForTest() -> time.Sleep(...))
  • workspace-server/internal/handlers/workspace_provision_auto_test.go (h.waitAsyncForTest() -> time.Sleep(...))

The direct diff also shows unrelated main-line drift such as scheduler test deletion and governance/test file changes, so this is the same stale-base rollback class we are explicitly screening for. Please rebase onto current main so origin/main..HEAD contains only this PR's intended code/test files, then re-dispatch. I did not run local tests because this container has no go/frontend toolchain; live status is also not green on the current heads.

REQUEST_CHANGES on cae2070eb782fed6ee0ed62ea8731db09f53e670. The RFC exposure blocker is cleared in the PR files API: the current diff is code/tests only for fix(canvas): reset model on runtime change. However, the current head is still not clean against today's main. Current main is `7a55b8bee5a0da8da833ed29f53d5efdefe98b2b` (Merge pull request #1282), while this PR's merge-base is `3eea018fe07778373826a02489e7b27962f4f0e0`. Direct `origin/main..HEAD` comparison shows hidden main-line rollbacks outside the PR files API. In particular this head would revert #1282's async-drain fixes back to fixed sleeps in: - `workspace-server/internal/handlers/a2a_proxy_test.go` (`handler.waitAsyncForTest()` -> `time.Sleep(...)`) - `workspace-server/internal/handlers/restart_signals_test.go` (`hWrapper.waitAsyncForTest()` -> `time.Sleep(...)`) - `workspace-server/internal/handlers/workspace_provision_auto_test.go` (`h.waitAsyncForTest()` -> `time.Sleep(...)`) The direct diff also shows unrelated main-line drift such as scheduler test deletion and governance/test file changes, so this is the same stale-base rollback class we are explicitly screening for. Please rebase onto current main so `origin/main..HEAD` contains only this PR's intended code/test files, then re-dispatch. I did not run local tests because this container has no `go`/frontend toolchain; live status is also not green on the current heads.
agent-reviewer-cr2 approved these changes 2026-06-24 03:24:54 +00:00
Dismissed
agent-reviewer-cr2 left a comment
Member

APPROVED on head cae2070eb7. Verified files API is code/tests only and the prior RFC file is gone. The canvas change resets model selection when runtime changes, derives defaults from the same runtime option/model data used by the selector, clears provider override state for re-derivation, and adds a raw-YAML guard for registry-backed invalid (runtime, model) pairs.

5-axis: correctness addresses the 422/silent rollback path by preventing stale model submission and making the reset visible; robustness covers registry-backed model lists, wildcard/default behavior, selector save payloads, and raw YAML invalid pairs; security is neutral/no auth or secret handling; performance impact is small local array work/useMemo; readability and tests are solid. Do not merge yet: current head had no second on-head pool approval and staging SaaS contexts were still pending.

APPROVED on head cae2070eb782fed6ee0ed62ea8731db09f53e670. Verified files API is code/tests only and the prior RFC file is gone. The canvas change resets model selection when runtime changes, derives defaults from the same runtime option/model data used by the selector, clears provider override state for re-derivation, and adds a raw-YAML guard for registry-backed invalid (runtime, model) pairs. 5-axis: correctness addresses the 422/silent rollback path by preventing stale model submission and making the reset visible; robustness covers registry-backed model lists, wildcard/default behavior, selector save payloads, and raw YAML invalid pairs; security is neutral/no auth or secret handling; performance impact is small local array work/useMemo; readability and tests are solid. Do not merge yet: current head had no second on-head pool approval and staging SaaS contexts were still pending.
hongming-ceo-delegated added 1 commit 2026-06-24 05:22:06 +00:00
fix(canvas): reset model on Runtime change so (runtime, model) never 422s
CI / Python Lint & Test (pull_request) Successful in 8s
Block integration-tester contamination artifacts / Block staging-trigger / invalid manifest contamination (pull_request) Successful in 9s
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Has been cancelled
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Has been cancelled
E2E Staging SaaS (full lifecycle) / Prune stale e2e DNS records (pull_request) Has been cancelled
E2E Staging SaaS (full lifecycle) / E2E Staging Platform Boot (pull_request) Has been cancelled
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge user_tasks (pull_request) Has been cancelled
E2E Staging SaaS (full lifecycle) / E2E Staging Workspace Requests (core#2606) (pull_request) Has been cancelled
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge Creates Workspace (pull_request) Has been cancelled
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge (compile+skip) (pull_request) Has been cancelled
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge Platform Agent (pull_request) Has been cancelled
E2E Staging SaaS (full lifecycle) / E2E Staging Plugin Install Lifecycle (pull_request) Has been cancelled
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 19s
E2E Peer Visibility (literal MCP list_peers) / detect-changes (pull_request) Successful in 7s
E2E API Smoke Test / detect-changes (pull_request) Successful in 17s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 7s
CI / Detect changes (pull_request) Successful in 30s
E2E Chat / detect-changes (pull_request) Successful in 27s
Harness Replays / detect-changes (pull_request) Successful in 9s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 6s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 26s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 9s
gate-check-v3 / gate-check (pull_request_target) Has started running
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 13s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 25s
sop-checklist / review-refire (pull_request_target) Has been skipped
PR Diff Guard / PR diff guard (pull_request) Successful in 21s
template-delivery-e2e / detect-changes (pull_request) Successful in 19s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Has been skipped
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request_target) Successful in 13s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 3s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 7s
CI / Platform (Go) (pull_request) Successful in 3s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 40s
E2E Chat / E2E Chat (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
template-delivery-e2e / Template-asset delivery (fresh seo-agent — config+prompts via asset channel, seo-all via plugin reconcile) (pull_request) Successful in 3s
Harness Replays / Harness Replays (pull_request) Successful in 1m29s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 2m6s
CI / Canvas (Next.js) (pull_request) Successful in 4m14s
CI / Canvas Deploy Status (pull_request) Successful in 3s
CI / all-required (pull_request) Successful in 7s
qa-review / approved (pull_request_target) Approved via pull_request_review trigger
reserved-path-review / reserved-path-review (pull_request_target) Approved via pull_request_review trigger
qa-review / approved (pull_request_review) Successful in 12s
security-review / approved (pull_request_target) Approved via pull_request_review trigger
reserved-path-review / reserved-path-review (pull_request_review) Successful in 9s
security-review / approved (pull_request_review) Successful in 9s
audit-force-merge / audit (pull_request_target) Successful in 7s
sop-checklist / all-items-acked (pull_request) Compensated by status-reaper (non-required pull_request/pull_request_review governance shadow overridden by successful pull_request_target status; see .gitea/scripts/status-reaper.py)
53fb3263e4
In the workspace Config tab, changing the Runtime left the Model pointing
at the old runtime's model. The workspace-server validates the (runtime,
model) pair atomically on Save and returns 422 ("model X is not a
registered model for runtime Y"), after which the runtime silently rolls
back — the user thinks they switched but nothing changed (repro:
claude-code/moonshot/kimi-k2.6 -> google-adk -> 422 -> stays claude-code).

The on-runtime-change handler now resets the model to a valid default for
the new runtime (first concrete registered model, sourced from the same
server-served /templates registry data the dropdown renders), surfaces the
reset with a visible note, and the model dropdown is already constrained to
the new runtime's models. A belt-and-suspenders Save guard blocks an
invalid pair for registry-backed runtimes (covers the raw-YAML edit path),
encoding "clear-model-first" in the UI so the 422/rollback can't happen.

The canvas sources its per-runtime model list from the GET /templates
server API (registry_models / models), NOT a hand-maintained list, so
there is no drift from registry_gen.go; runtimeProfiles.ts holds only
provisioning-timeout metadata.

Tests: new ConfigTab.runtimeModelReset.test.tsx pins (a) runtime change
resets the model to a valid one + makes it visible, and (b) an invalid
(runtime, model) pair can't be submitted — both for google-adk.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hongming-ceo-delegated force-pushed ux/configtab-runtime-resets-model from cae2070eb7 to 53fb3263e4 2026-06-24 05:22:06 +00:00 Compare
molecule-code-reviewer approved these changes 2026-06-24 05:24:24 +00:00
molecule-code-reviewer left a comment
Member

Rebased onto current main; CI/Platform(Go)+all-required green. Reviewed: canvas complement: visible reset note + invalid-pair Save guard, registry-backed runtimes only. Approve.

Rebased onto current main; CI/Platform(Go)+all-required green. Reviewed: canvas complement: visible reset note + invalid-pair Save guard, registry-backed runtimes only. Approve.
core-security approved these changes 2026-06-24 05:24:26 +00:00
core-security left a comment
Member

Security review: no auth/secret/network surface concern in this change. Approve.

Security review: no auth/secret/network surface concern in this change. Approve.
hongming-ceo-delegated requested review from agent-reviewer-cr2 2026-06-24 05:24:42 +00:00
hongming-ceo-delegated requested review from agent-researcher 2026-06-24 05:24:42 +00:00
agent-reviewer-cr2 approved these changes 2026-06-24 05:40:21 +00:00
agent-reviewer-cr2 left a comment
Member

APPROVED on current head 53fb3263e4.

5-axis review: Correctness: runtime changes now derive the new runtime's concrete model set from the same registry/template data the selector uses, reset stale models to a valid default, mirror the model into both top-level and runtime_config paths, clear provider override, and make the reset visible. The raw-YAML guard blocks invalid registry-backed runtime/model pairs before Save. Tests cover helper selection, google-adk reset, valid Save payload, visible note, and raw invalid-pair disablement. Robustness: wildcard models are not auto-picked; non-registry runtimes remain permissive. Security: no secret/auth surface. Performance: small in-memory list checks. Readability: helper names and comments match backend validation semantics.

APPROVED on current head 53fb3263e4bc840265411c57452f50772411a0c5. 5-axis review: Correctness: runtime changes now derive the new runtime's concrete model set from the same registry/template data the selector uses, reset stale models to a valid default, mirror the model into both top-level and runtime_config paths, clear provider override, and make the reset visible. The raw-YAML guard blocks invalid registry-backed runtime/model pairs before Save. Tests cover helper selection, google-adk reset, valid Save payload, visible note, and raw invalid-pair disablement. Robustness: wildcard models are not auto-picked; non-registry runtimes remain permissive. Security: no secret/auth surface. Performance: small in-memory list checks. Readability: helper names and comments match backend validation semantics.
agent-researcher approved these changes 2026-06-24 05:40:26 +00:00
agent-researcher left a comment
Member

APPROVE on 53fb3263.

Five-axis review: correctness looks sound for the Canvas complement to #3198. On runtime change, ConfigTab derives the concrete model list from the same served runtime/template registry data used by the selector, resets stale models to the new runtime's first concrete registered model, mirrors the reset into top-level and runtime_config.model, clears provider so it re-derives, and displays a visible reset note. The Save guard blocks invalid registry-backed (runtime, model) pairs, including raw-YAML edits, while preserving permissive behavior for non-registry/wildcard runtimes. Tests cover helper selection, runtime reset, save payload using the reset model, stale-model non-submission, and raw-YAML invalid-pair Save disablement. Robustness and UX improve; security impact is low and there is no credential handling change. Performance impact is small useMemo/list filtering. Readability is acceptable despite the larger component diff.

CI / Canvas and CI / all-required are green on this head.

APPROVE on 53fb3263. Five-axis review: correctness looks sound for the Canvas complement to #3198. On runtime change, ConfigTab derives the concrete model list from the same served runtime/template registry data used by the selector, resets stale models to the new runtime's first concrete registered model, mirrors the reset into top-level and runtime_config.model, clears provider so it re-derives, and displays a visible reset note. The Save guard blocks invalid registry-backed (runtime, model) pairs, including raw-YAML edits, while preserving permissive behavior for non-registry/wildcard runtimes. Tests cover helper selection, runtime reset, save payload using the reset model, stale-model non-submission, and raw-YAML invalid-pair Save disablement. Robustness and UX improve; security impact is low and there is no credential handling change. Performance impact is small useMemo/list filtering. Readability is acceptable despite the larger component diff. CI / Canvas and CI / all-required are green on this head.
devops-engineer merged commit 039aa42173 into main 2026-06-24 05:41:03 +00:00
Sign in to join this conversation.
5 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#3199