fix(claude): respect profile models for built-in claude provider#1311
fix(claude): respect profile models for built-in claude provider#1311ilteoood wants to merge 5 commits into
Conversation
The built-in Claude provider was the only one in the registry whose profile `models` array was treated as additive on top of the hardcoded first-party catalog. Every other built-in provider (and every custom provider) replaces the runtime model list when `models` is set, which matches the documented behavior in docs/custom-providers.md. For users pointing Claude Code at a third-party Anthropic-compatible gateway (Z.AI, Alibaba/Qwen, MiniMax, custom proxies, …) and curating the picker with `agents.providers.claude.models`, the old behavior leaked the nine first-party Claude models into the dropdown, making it impossible to ship a curated list. This is issue getpaseo#1299. The fix flips the built-in Claude entry in `provider-registry.ts` to match the other providers, so `models` replaces the runtime catalog (including the settings.json-discovered entries surfaced by getClaudeModelsWithSettings). The existing `additionalModels` field keeps its additive semantics for anyone who still wants to append entries on top of the first-party list. - provider-registry.ts: drop the claude-only `profileModelsAreAdditive` branch and align with the rest of the registry. - provider-registry.test.ts: update the "append to runtime models" expectation for built-in Claude to "replace runtime models" and add a regression test that mirrors the issue scenario (hardcoded catalog + configured profile models, expect only the profile models). - agent.test.ts: make the "returns hardcoded claude models" hermetic by pointing CLAUDE_CONFIG_DIR at an empty temp dir; the test was reading the host's real ~/.claude/settings.json (which now contains MiniMax env vars from the issue report) and leaking that into the assertion. - custom-providers.md: correct the note about Claude profile models being additive and document the new replace semantics alongside additionalModels. Closes getpaseo#1299
|
| Filename | Overview |
|---|---|
| packages/server/src/server/agent/provider-registry.ts | One-line fix removes the Claude-only profileModelsAreAdditive: true branch, aligning its model-resolution path with every other built-in provider. Clean and correct. |
| packages/server/src/server/agent/providers/claude/models.ts | Adds optional configDir parameter to getClaudeModelsWithSettings and the private readClaudeSettingsModels/resolveClaudeConfigDir helpers to enable injection-based test isolation. Logic is unchanged. |
| packages/server/src/server/agent/providers/claude/agent.ts | Threads configDir from ClaudeAgentClientOptions into listModels. listPersistedAgents still resolves the config dir from process.env directly, so the injected seam is incomplete for that method. |
| packages/server/src/server/agent/provider-registry.test.ts | Existing "append" test flipped to "replace" semantics and a new regression test for the issue-1299 scenario added. Both tests are well-scoped and cross the module interface. |
| packages/server/src/server/agent/providers/claude/agent.test.ts | Made hermetic by injecting configDir pointing at a fresh temp dir instead of mutating process.env.CLAUDE_CONFIG_DIR. Uses try/finally for correct cleanup. |
| docs/custom-providers.md | Corrects the documentation for Claude profile models from additive to replace semantics, and documents additionalModels as the escape hatch for the old additive behavior. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Profile override has 'models' set?] -->|No| B[Use runtime models from getClaudeModelsWithSettings]
A -->|Yes - Before fix| C_old["Append profile models ON TOP of runtime models\n(Claude-only exception: profileModelsAreAdditive=true)"]
A -->|Yes - After fix| D["Replace runtime models with profile models\n(same as all other providers: profileModelsAreAdditive=false)"]
B --> E[Merge additionalModels on top]
C_old --> E
D --> E
E --> F[Return final model list to picker]
style C_old fill:#f88,stroke:#c00
style D fill:#8f8,stroke:#080
Reviews (5): Last reviewed commit: "Merge branch 'main' into feature/1299-re..." | Re-trigger Greptile
…rmeticity Greptile flagged the previous hermeticity fix on agent.test.ts: mutating process.env.CLAUDE_CONFIG_DIR inside a test leaks the redirection into any concurrent test in the same process for the duration of the try block. Thread a `configDir` option through ClaudeAgentClient -> getClaudeModelsWithSettings -> readClaudeSettingsModels -> resolveClaudeConfigDir instead. The constructor follows the same injection pattern as `resolveBinary`, so the test passes an empty temp dir via the option and no shared global state is touched. - models.ts: add an optional `configDir` to getClaudeModelsWithSettings, readClaudeSettingsModels, and resolveClaudeConfigDir. Env var remains the fallback for production callers. - agent.ts: add `configDir?` to ClaudeAgentClientOptions, store it on the instance, and forward it to getClaudeModelsWithSettings from listModels. - agent.test.ts: drop the process.env save/mutate/restore dance and pass `configDir: emptyConfigDir` to the constructor instead. Refs getpaseo#1311
Summary
Fixes #1299.
The built-in
claudeprovider was the only one in the registry whose profilemodelsarray was treated as additive on top of the hardcoded first-party catalog (claude-opus-4-8,claude-sonnet-4-6,claude-haiku-4-5, …). Every other built-in provider — and every custom provider that declaresmodels— already replaces the runtime model list, which matches the documented behavior indocs/custom-providers.md.For users pointing Claude Code at a third-party Anthropic-compatible gateway (Z.AI, Alibaba/Qwen, MiniMax, custom proxies, …) and curating the picker with
agents.providers.claude.models, the old behavior leaked all nine first-party Claude models into the dropdown, making it impossible to ship a curated list.What changed
packages/server/src/server/agent/provider-registry.ts— drop the claude-onlyprofileModelsAreAdditivebranch and align with the rest of the registry.agents.providers.claude.modelsnow replaces the runtime catalog (including the entries discovered from~/.claude/settings.jsonviagetClaudeModelsWithSettings). TheadditionalModelsfield keeps its additive semantics for anyone who still wants to append entries on top of the first-party list.packages/server/src/server/agent/provider-registry.test.ts— flip the existing built-in Claude "append to runtime models" expectation to "replace runtime models" and add a regression test that mirrors the issue scenario: hardcoded catalog + configured profilemodels, expect only the profile models.packages/server/src/server/agent/providers/claude/agent.test.ts— make the "returns hardcoded claude models" test hermetic by pointingCLAUDE_CONFIG_DIRat an empty temp dir. The test was reading the host's real~/.claude/settings.jsonand leaking itsANTHROPIC_*_MODELentries into the assertion.docs/custom-providers.md— correct the note about Claude profile models being additive and document the new replace semantics alongsideadditionalModels.How to verify
Open the agent session picker → the dropdown should now show only the two configured models, with
MiniMax-M3as the default. Noclaude-opus-4-8,claude-sonnet-4-6,claude-haiku-4-5, etc. should leak in.Verification
npm run lint→ 0 warnings, 0 errorsnpm run format:check→ cleannpm run typecheck→ passes for all workspacesnpx vitest run src/server/agent/provider-registry.test.ts→ 28/28 pass (includes a new test mirroring the issue scenario)npx vitest run src/server/agent/providers/claude/models.test.ts→ 13/13 passnpx vitest run src/server/agent/provider-snapshot-manager.test.ts→ 23/23 passreturns hardcoded claude modelstest now passes after being made hermetic (it was reading the host's real~/.claude/settings.jsonand failing on any machine that has theANTHROPIC_*_MODELenv vars set).Closes #1299