fix(canvas): reset model on Runtime change so (runtime, model) pair never 422s + silently rolls back #3199
Reference in New Issue
Block a user
Delete Branch "ux/configtab-runtime-resets-model"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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 togoogle-adk→ Save → 422 → runtime staysclaude-code.Fix
The on-runtime-change handler now:
selectedRuntime).role="status"note ("Model reset to X because Y isn't available for this runtime").SSOT / drift note
The canvas sources its per-runtime model list from the
GET /templatesserver API (registry_models/models), which the workspace-server derives fromregistry_gen.go'sRuntimesmap — not a hand-maintained frontend list. So there is no drift to align:runtimeProfiles.tsholds only provisioning-timeout metadata, not model data. (If a sync ever became necessary, the right place would be the server's/templatesprojection, not the canvas.)Tests
New
canvas/src/components/tabs/__tests__/ConfigTab.runtimeModelReset.test.tsx(google-adk):(runtime, model)pair (raw-YAMLgoogle-adk+moonshot/kimi-k2.6) can't be submitted — Save is disabled and no/modelPUT fires.modelIdsForRuntime/defaultModelForRuntime.Commands:
npx vitest run(canvas) — 3515 pass incl. 6 new + 9 existing ConfigTab tests; 2 unrelated pre-existing failures (@novnc/novncunresolved in this checkout, identical on base).tsc --noEmit— 0 new errors (229 pre-existing test-file errors, identical on base).eslinton touched files — clean.🤖 Generated with Claude Code
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.
73599ea866tocae2070eb7REQUEST_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 is3eea018fe07778373826a02489e7b27962f4f0e0.Direct
origin/main..HEADcomparison 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..HEADcontains only this PR's intended code/test files, then re-dispatch. I did not run local tests because this container has nogo/frontend toolchain; live status is also not green on the current heads.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.
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>cae2070eb7to53fb3263e4Rebased onto current main; CI/Platform(Go)+all-required green. Reviewed: canvas complement: visible reset note + invalid-pair Save guard, registry-backed runtimes only. Approve.
Security review: no auth/secret/network surface concern in this change. Approve.
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.
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.