You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[AIGTWY-4710] Keep managed models across config transitions (#709)
Keeps a coding agent's managed model settings in sync with the managed
config across workspace transitions (AIGTWY-4710). Switching between
managed configs reconciles each agent's model settings to the current
config, and configuring a workspace with no managed config clears ug's
managed model settings so an unmanaged workspace never enforces a stale
list inherited from a previous managed workspace.
Lifecycle exercised, per agent (functional in-process tests plus
end-to-end integration journeys):
- no managed config: no picker (Claude) / no catalog (Codex).
- static config A: the picker/catalog lists exactly A's models.
- static config B: reconciles to B; models A listed that B omits are
pruned.
- model discovery via a Model Provider Service: the picker/catalog is
cleared, since the `Databricks-Model-Provider-Service` header routes and
there is no static list.
- no managed config again: the picker/catalog is cleared, not inherited.
Changes:
- Claude clears the managed picker (`availableModels`,
`enforceAvailableModels`, `modelPicker`) whenever ug is not writing a
static list this run (an unmanaged workspace or a provider switch). It
always clears the picker in its own `ucode-settings.json`. In the OS
`managed-settings.json` the picker is shared with the administrator, so
ug treats it as one unit and reverts it to its pre-ucode baseline only
when the live picker is unchanged since ug last wrote it (a three-way
merge of baseline, ug's last write, and the live file). That way ug
removes only what it introduced and never an administrator's own picker,
a picker they added after ug cleared its own, or one they later edited
(the enforce flag included).
- Codex clears its generated catalog and `model_catalog_json` pointer on
an unmanaged configure or a provider switch, so an unmanaged workspace
uses its own discovery. A managed static config writes them; the
launch-time `model` pin follows Codex's existing clear-when-unmanaged
behavior.
- Prettifies the Claude `/model` picker labels: the enforced allow-list
still shows one row per model (it can include multiple versions per
family and non-Claude models), but each label is now title-cased with a
dotted version and known acronyms upper-cased
(`system.ai.claude-haiku-4-5` shows as `Claude Haiku 4.5`,
`system.ai.glm-5-3` as `GLM 5.3`) instead of the raw id. The routable id
still shows on the right of each row.
- The `UCODE_MANAGED_CONFIG_STUB` test hook accepts an explicit JSON
`null` to reproduce a workspace with no managed config, distinct from an
unset or unreadable stub (which still falls through to the real HTTP
read).
- Fixes a pre-existing bug where a managed config's per-family default
models were dropped. `managed_resolve._manifest_models` read a stale
`models` key instead of the normalized `default_models_by_model_family`,
so `managed_claude_family_models` returned nothing and no
`ANTHROPIC_DEFAULT_<family>_MODEL` was written (only the overall default
and the picker survived, on separate paths). Unit fixtures that
hand-built the stale shape masked it; the fix corrects them and adds a
regression test that builds through `normalize_managed_config` so the
shape can't drift again.
- Reconciles the per-family default env keys the same three-way way as
the picker so a workspace switch does not carry stale defaults over.
Each `ANTHROPIC_DEFAULT_<family>_MODEL` resolves as config, then an
administrator-authored value (one that differs from ug's last write,
preserved verbatim), then discovery; a value ug itself wrote is
re-derived from the new workspace rather than preserved, and any family
default whose model is not in the enforced `availableModels` list is
dropped (e.g. a discovered `fable` default when the admin's list omits
fable).
- Adds functional lifecycle tests (both agents) covering the full
sequence above, including the per-family default env keys landing (opus
and sonnet carry ug's `[1m]` context tag), per-agent `managed_fixture`
integration journeys running it through `ug configure` against the
managed e2e workspace, and coverage-matrix updates. The integration MPS
states point at real provider services on that workspace:
`main.default.ci_e2e_anthropic_mps` (Claude) and
`main.default.ci_e2e_openai_mps` (Codex).
What Claude Code writes for models, by managed model source:
- static (`model_services`): sets `availableModels`,
`enforceAvailableModels`, and `modelPicker` in
`~/.claude/ucode-settings.json` and the OS `managed-settings.json`. The
managed default is applied at launch as `env.ANTHROPIC_MODEL`.
- model discovery via MPS (`model_provider_service`): writes no model
list and clears ug's static picker; the
`Databricks-Model-Provider-Service` header routes and Claude shows its
own model names. A Bedrock-backed provider pins
`ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU}_MODEL` from the manifest's family
slots; an Anthropic provider pins nothing.
- model discovery via `unity_catalog_location`: not consumed on main
(schema only; no fields written).
What Codex writes for models, by managed model source:
- static (`model_services`): writes the catalog file
`~/.ucode/codex-model-catalog.json` and points `ucode.config.toml`'s
`model_catalog_json` at it. The managed default is pinned as `model` in
`ucode.config.toml`.
- model discovery via MPS (`model_provider_service`): writes no catalog
and clears any static catalog; the `Databricks-Model-Provider-Service`
header routes and Codex discovers models at launch.
- model discovery via `unity_catalog_location`: not consumed on main
(schema only; no fields written).
Known limitations (out of scope here):
- Clearing on an unmanaged configure also resets in the rare case where
the same workspace's managed config genuinely disappeared, since ug does
not distinguish that from an intentional switch to an unmanaged
workspace.
- On a non-interactive configure (no sudo, `managed_writes_allowed()`
False), ug cannot rewrite the root-owned OS `managed-settings.json`, so
a stale picker there is not cleared in that path; the private
`ucode-settings.json` still is. The interactive flow clears both.
- If ug's managed-file snapshots (its pre-ucode baseline or last write)
are unreadable, ug leaves the managed-file picker in place rather than
risk clobbering an admin's entry.
This pull request and its description were written by Isaac.
Co-authored-by: Isaac <no-reply@databricks.com>
Copy file name to clipboardExpand all lines: tests/README.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -52,6 +52,7 @@ All tests live directly in `integration/`; shared mechanics live in `utils/`.
52
52
|`test_ug_configure_claude_rejects_invalid_credentials`, `test_ug_configure_codex_rejects_invalid_credentials`| Configure with a rejected bearer against the real workspace | Authentication failure; no successful saved setup |
53
53
|`test_ug_configure_managed_claude`, `test_ug_configure_managed_codex`| Configure against a workspace that publishes a managed CodingAgentConfig | No agent selector; each agent's generated config exposes exactly the admin's static model_services; real gateway prompt on launch |
54
54
|`test_ug_configure_managed_codex_catalog_fallback`| Configure from an injected managed response containing a GPT model absent from Codex's bundled catalog | Actionable metadata warning; conservative catalog entry for the unknown model; real Codex prompt on the valid default model |
55
+
|`test_managed_fixture_claude_model_lifecycle`, `test_managed_fixture_codex_model_lifecycle`| Configure across no config -> static A -> static B -> MPS -> no config (stub-injected, `null` for no-config; MPS via a real provider service) | Each agent's model files reconcile to each static config (removed models pruned); switching to an MPS and a workspace with no managed config both clear ug's managed model settings so no stale list is enforced |
55
56
|`test_ug_installed_wheel_exposes_help_and_version`| Invoke freshly installed console command | Package version matches; public help works |
56
57
|`test_ug_status_in_fresh_home_is_unconfigured`| Request status before configure | Unconfigured status |
57
58
|`test_ug_auth_without_configuration_explains_how_to_configure`| Request auth before configure | Actionable setup error and nonzero exit |
@@ -60,7 +61,7 @@ All tests live directly in `integration/`; shared mechanics live in `utils/`.
60
61
61
62
With both agents selected there are **41 live cases** (6 interactive TUI cases),
62
63
**3 managed-workspace cases** (marker `managed`, run against a separate workspace that
63
-
publishes a CodingAgentConfig), **3 managed-fixture cases** (marker `managed_fixture`, with only
64
+
publishes a CodingAgentConfig), **5 managed-fixture cases** (marker `managed_fixture`, with only
64
65
the CodingAgentConfig input injected), and **5 installation checks**. Parametrization varies
65
66
argument spelling or routing mode, never hides the agent/provider in the test name. Duplicate boot-only cases
66
67
are incorporated into the Databricks configuration TUI journeys.
0 commit comments