Skip to content

[Bug]: Claude model picker not synchronized with Claude Code model list, unlike Codex #13875

Description

@rodriguezst

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Configure a Claude provider to use an Anthropic-compatible gateway/proxy such as CLIProxyAPI via ANTHROPIC_BASE_URL.
  2. Enable Claude Code gateway model discovery:
    CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
  3. Ensure the gateway exposes custom model IDs from its /v1/models endpoint.
  4. Start Claude Code with the same environment and run /model.
  5. Confirm the gateway-provided/custom models appear in Claude Code’s /model picker.
  6. Start/use the same Claude provider from T3 Code.
  7. Open T3 Code’s model picker.

Expected behavior

T3 Code should show the model inventory reported by the running Claude Code instance / Claude Agent SDK.

When Claude Code gateway discovery is enabled, models discovered from the gateway’s /v1/models endpoint and visible in Claude Code’s /model picker should also be selectable in T3 Code.

This should be provider-agnostic: T3 should consume the models Claude Code reports rather than implement CLIProxyAPI-specific or gateway-specific discovery.

If live model discovery is unavailable, T3 can continue falling back to its bundled Claude model catalog and manually configured custom models.

Actual behavior

Claude Code correctly discovers the gateway models and shows them in /model, but T3 Code’s Claude model picker does not show them.

T3 currently builds the Claude picker from its bundled/version-filtered Claude model catalog plus customModels, instead of using the model inventory already returned during Claude Agent SDK initialization.

This differs from the Codex integration, where T3 obtains the available models dynamically from Codex.

The mismatch is particularly visible with Anthropic-compatible proxies/gateways. For example, CLIProxyAPI can expose additional model IDs through /v1/models; Claude Code sees them when CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 is enabled, but T3 still presents its static Claude catalog unless every model is added manually in T3 settings.

The Claude provider already calls initializationResult() during its capability probe, so the SDK-reported model list should be available without T3 making a separate request to the gateway.

Impact

Cosmetic issue

Version or commit

main @ 75d63d6

Environment

No response

Logs or stack traces

Screenshots, recordings, or supporting files

No response

Workaround

Add every gateway model manually under the Claude provider’s custom models in T3 Code.

This works for a fixed list, but duplicates model discovery that Claude Code already performs and becomes stale when the gateway’s available models change.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Sep 26, 2026
  2. juliusmarminge commented on Sep 26, 2026

    @juliusmarminge
    Member

    Triage: #13875

    Verdict: Accept
    Type: Feature request
    Duplicate: No
    Already implemented: No
    Confidence: High
    Suggested labels: enhancement, accepted
    Scope: Medium. The Claude status probe already starts an SDK session and reads initializationResult(). It has to keep that model list and merge unknown ids into the provider snapshot as built-in rows. Known catalog entries should keep T3's effort, fast-mode, and context-window descriptors. The web picker drops any row marked custom, so gateway ids cannot be stored that way.

    What was asked

    When Claude Code is pointed at an Anthropic-compatible gateway (ANTHROPIC_BASE_URL) and CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 is set, /model in Claude Code lists the gateway's models. T3's Claude picker does not. The ask is to use the model inventory the running Claude Code process already reports, the same way Codex does, and to keep the bundled catalog plus manual custom models when that inventory is missing. Not a CLIProxyAPI-specific client.

    What the code does today

    Confirmed on current main (d110f98670). The reported commit 75d63d64 is an ancestor, and nothing since then has changed this path. This is a missing capability, not a regression. The picker is doing what it was built to do.

    checkClaudeProviderStatus builds the picker from the version-filtered bundled catalog plus customModels:

      const models = providerModelsFromSettings(
        resolveClaudeModelsForVersion(modelCatalog, parsedVersion),
        claudeSettings.customModels,
        DEFAULT_CLAUDE_MODEL_CAPABILITIES,
      );

    resolveClaudeModelsForVersion only drops catalog rows whose minVersion / maxVersionExclusive do not match claude --version. It never asks Claude Code which models exist.

    The capability probe does call initializationResult(), and the instance environment is the env that probe uses (mergeProviderInstanceEnvironment in ClaudeDriver, then makeClaudeEnvironment). ANTHROPIC_BASE_URL and CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY set on the provider instance are therefore already in the subprocess. The probe then keeps account, slash commands, and usage, and drops models:

            return {
              email: account?.email,
              subscriptionType: account?.subscriptionType,
              tokenSource: account?.tokenSource,
              apiProvider: account?.apiProvider,
              slashCommands: parseClaudeInitializationCommands(init.commands),
              ...(usage ? { usage } : {}),
            } satisfies ClaudeCapabilitiesProbe;

    The Agent SDK's SDKControlInitializeResponse includes models: ModelInfo[] (value, displayName, description, and optional resolvedModel, supportsEffort, supportedEffortLevels, supportsFastMode, supportsAdaptiveThinking). supportedModels() is that same init snapshot. T3 does not read either.

    Codex is the contrast the report describes. requestAllCodexModels pages model/list and publishes those rows with isCustom: false. Custom slugs are appended afterward.

    The web picker will not show a Claude row that is marked custom. getAppModelOptions and getAppModelOptionsForInstance take only !model.isCustom from the snapshot, then rebuild custom rows from settings. Gateway ids have to be non-custom snapshot rows or they never appear unless they are also typed into custom models.

    The manual-custom-model workaround in the report is the path that exists today. It goes stale when the gateway's /v1/models list changes.

    Not a duplicate

    No open issue asks T3 to follow Claude Code's model inventory or gateway discovery. Nearby closed work is different: #9383 maps a gateway-prefixed slug onto an existing catalog entry, #8964 and #4194 are effort controls on custom Claude models, #11369 is OpenRouter docs. #8652 (closed, not merged) would hide org-restricted catalog models. It would not add models the catalog does not already contain.

    Implementation notes

    On a successful probe, merge init.models into the snapshot:

    • Match value and resolvedModel to catalog slugs and aliases. Keep T3's descriptors for those rows (effort map, context-window tokens, model suffixes). The SDK flags can fill effort and fast mode only where the catalog row has none.
    • Append ids that are not in the catalog as isCustom: false, with capabilities taken from ModelInfo when present. Runtime resolution already passes unknown slugs through to Claude Code verbatim, which is what custom models do.
    • If the probe fails or models is empty, keep the current version-filtered catalog plus customModels.
    • Leave the user's custom models as the settings-owned list. Do not write discovered ids back into settings.

    Check a cold CLAUDE_CONFIG_DIR. The probe aborts the subprocess as soon as initializationResult() resolves, and Claude Code has not always finished gateway /v1/models discovery before that handshake. A cache warmed by an interactive claude session can hide that race. The capabilities cache also lives for 5 minutes, so a list that arrives late will not show up until the next probe.

    Discovery stays Claude Code's job. T3 should not call the gateway's /v1/models itself.

  3. added
    enhancementRequested improvement or new capability.
    acceptedfeature request accepted
    via-triageFiled through npx t3 triage
    and removed
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Sep 26, 2026
  4. added 2 commits that reference this issue on Sep 27, 2026
    7e7de7b
    65817fa
  5. added a commit that references this issue on Oct 1, 2026
    6a236ec
  6. added 4 commits that reference this issue on Oct 2, 2026
    8ee8c12
    09ca56c
    1e075b6
    c1fdd59
  7. kaovilai commented on Oct 9, 2026

    @kaovilai

    Note

    Responses generated with Claude

    Adding a related case that seems to belong under this issue (and the approach in #13913) rather than a separate one: reported capabilities only apply to ids the catalog doesn't already know.

    withClaudeReportedModels skips any id that matches a catalog slug or alias. For known models, the picker is driven entirely by the hand-written profile in model-manifest.json, whatever Claude Code reports. That matches the triage note above (known entries keep T3's descriptors), but it leaves a gap for options that depend on what the model supports.

    Example: the Ultracode reasoning option

    • It is listed by hand only on the Opus 4.8, Opus 5, Opus 5.5 and Fable 5 profiles.
    • Claude Code does not gate Ultracode by model family. Its model-config docs say Ultracode is unavailable when workflows are off or "the model doesn't support xhigh effort" (https://code.claude.com/docs/en/model-config). The same docs list Claude Sonnet 5.5 among the models that support xhigh.
    • On a current Claude Code build, /effort ultracode on Sonnet 5.5 is accepted ("Ultracode on (this session only): dynamic workflows on every task. Effort stays high."). The T3 picker for that model has no Ultracode entry, even though the profile already lists Extra High.

    Suggestion: derive whether Ultracode is offered from the effort levels the model reports (xhigh present), for catalog and discovered models alike, instead of only from the hardcoded profile list. A discovered gateway model that reports xhigh would then get it too, and a new xhigh-capable model would not need a manifest edit. The existing ultracode to xhigh effort mapping would need to apply to discovered models as well.

    This is the same class of drift as #14193, where the profile's default effort for Sonnet 5.5 differs from the default Claude Code ships for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedenhancementRequested improvement or new capability.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions