Skip to content

fix(claude): respect profile models for built-in claude provider#1311

Open
ilteoood wants to merge 5 commits into
getpaseo:mainfrom
ilteoood:feature/1299-respect-claude-profile-models
Open

fix(claude): respect profile models for built-in claude provider#1311
ilteoood wants to merge 5 commits into
getpaseo:mainfrom
ilteoood:feature/1299-respect-claude-profile-models

Conversation

@ilteoood
Copy link
Copy Markdown
Contributor

@ilteoood ilteoood commented Jun 3, 2026

Summary

Fixes #1299.

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 (claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5, …). Every other built-in provider — and every custom provider that declares models — already replaces the runtime model list, 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 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-only profileModelsAreAdditive branch and align with the rest of the registry. agents.providers.claude.models now replaces the runtime catalog (including the entries discovered from ~/.claude/settings.json via getClaudeModelsWithSettings). The additionalModels field 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 profile models, expect only the profile models.
  • packages/server/src/server/agent/providers/claude/agent.test.ts — make the "returns hardcoded claude models" test hermetic by pointing CLAUDE_CONFIG_DIR at an empty temp dir. The test was reading the host's real ~/.claude/settings.json and leaking its ANTHROPIC_*_MODEL entries into the assertion.
  • docs/custom-providers.md — correct the note about Claude profile models being additive and document the new replace semantics alongside additionalModels.

How to verify

// ~/.paseo/config.json
{
  "agents": {
    "providers": {
      "claude": {
        "models": [
          { "id": "MiniMax-M2.7", "label": "MiniMax-M2.7" },
          { "id": "MiniMax-M3", "label": "MiniMax-M3", "isDefault": true }
        ]
      }
    }
  }
}

Open the agent session picker → the dropdown should now show only the two configured models, with MiniMax-M3 as the default. No claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5, etc. should leak in.

Verification

  • npm run lint → 0 warnings, 0 errors
  • npm run format:check → clean
  • npm run typecheck → passes for all workspaces
  • npx 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 pass
  • npx vitest run src/server/agent/provider-snapshot-manager.test.ts → 23/23 pass
  • The pre-existing returns hardcoded claude models test now passes after being made hermetic (it was reading the host's real ~/.claude/settings.json and failing on any machine that has the ANTHROPIC_*_MODEL env vars set).

Closes #1299

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
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 3, 2026

Greptile Summary

This PR fixes a long-standing inconsistency where the built-in claude provider was the only provider that treated agents.providers.claude.models as additive (appended on top of the hardcoded first-party catalog) rather than replacing the catalog — making it impossible to ship a curated-only model list when pointing Claude Code at a third-party gateway.

  • provider-registry.ts: Remove the one-line definition.id === \"claude\" guard that set profileModelsAreAdditive: true; Claude now follows the same replace-semantics as every other built-in and custom provider.
  • models.ts / agent.ts: Thread an optional configDir parameter down to getClaudeModelsWithSettings so tests can point the settings reader at an isolated temp directory instead of mutating process.env.CLAUDE_CONFIG_DIR.
  • provider-registry.test.ts / agent.test.ts: Flip the existing "append" assertion to "replace", add a regression test for the issue scenario, and make the hardcoded-models test hermetic.
  • docs/custom-providers.md: Correct the documented semantics and call out additionalModels as the opt-in for the old additive behavior.

Confidence Score: 5/5

Safe to merge — the change is a one-line removal of a Claude-specific exception, tests are updated and hermetic, and the documented behavior now matches the implementation.

The core change is a single boolean flip that aligns Claude with the rest of the registry. The configDir seam is a narrow, well-contained addition used only in listModels. All existing tests were updated to match the new semantics and a regression test covering the exact issue scenario was added. The only observation is a pre-existing inconsistency in listPersistedAgents where this.configDir is not used, but that method was untouched by this PR and carries no risk here.

No files require special attention.

Important Files Changed

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
Loading

Reviews (5): Last reviewed commit: "Merge branch 'main' into feature/1299-re..." | Re-trigger Greptile

Comment thread packages/server/src/server/agent/providers/claude/agent.test.ts Outdated
ilteoood added 4 commits June 3, 2026 14:02
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: models list not respected

1 participant