Skip to content

fix: resolve per-session model overrides using app session ID - #996

Open
Kennems wants to merge 2 commits into
siteboon:mainfrom
Kennems:fix/session-model-override
Open

fix: resolve per-session model overrides using app session ID#996
Kennems wants to merge 2 commits into
siteboon:mainfrom
Kennems:fix/session-model-override

Conversation

@Kennems

@Kennems Kennems commented Jul 11, 2026

Copy link
Copy Markdown

Problem

Selecting a model from the Chat UI (/model) stores the override in session-model-changes.json keyed by the app session ID, but two things prevented it from working:

  1. Display was staleresolveCommandModel (used by /model, /cost, /status) called getCurrentActiveModel which reads from the JSONL file, never checking the stored overrides. The UI always showed the old model after switching.

  2. Runtime lookup key mismatch — All provider runtimes (cursor, codex, opencode) called resolveResumeModel without appSessionId, so the lookup key (provider:providerNativeSessionId) never matched the stored key (provider:appSessionId).

  3. Env vars could override the modelANTHROPIC_MODEL and friends in process.env were forwarded to the SDK subprocess, potentially overriding the explicit model option.

Changes

  • commands.jsresolveCommandModel checks getChangedActiveModel (session-model-changes.json) before falling back to getCurrentActiveModel (JSONL/provider default). Fixes /model, /cost, /status showing stale model.

  • claude-sdk.js, cursor-cli.js, openai-codex.js, opencode-cli.js — Pass appSessionId to resolveResumeModel so the stored override is found.

  • claude-sdk.js — Strip ANTHROPIC_MODEL, ANTHROPIC_DEFAULT_OPUS_MODEL, ANTHROPIC_DEFAULT_SONNET_MODEL, ANTHROPIC_DEFAULT_HAIKU_MODEL from env forwarded to the SDK subprocess so the explicit model argument wins.

  • Test — Existing test resolveResumeModel uses appSessionId for model override lookup covers the core resolution logic.

Fixes #981

Summary by CodeRabbit

  • New Features

    • Improved session-specific model override handling across resumed conversations by using the app-level session id alongside provider session ids.
    • CLI/Provider model selection now preserves an explicit model choice instead of being overridden by Claude-related environment defaults.
  • Bug Fixes

    • Command model resolution now honors valid, user-selected per-session active-model overrides before falling back to the current active model or catalog default.
  • Tests

    • Added a unit test to verify app-level session overrides are correctly used during resume-model resolution.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 34d75b22-6438-4b8d-a1a6-f136de7724a6

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1141b and 03c7f1c.

📒 Files selected for processing (8)
  • server/claude-sdk.js
  • server/cursor-cli.js
  • server/modules/providers/services/provider-models.service.ts
  • server/modules/providers/tests/provider-models.service.test.ts
  • server/modules/websocket/services/chat-websocket.service.ts
  • server/openai-codex.js
  • server/opencode-cli.js
  • server/routes/commands.js
🚧 Files skipped from review as they are similar to previous changes (8)
  • server/openai-codex.js
  • server/cursor-cli.js
  • server/opencode-cli.js
  • server/routes/commands.js
  • server/modules/providers/tests/provider-models.service.test.ts
  • server/modules/providers/services/provider-models.service.ts
  • server/modules/websocket/services/chat-websocket.service.ts
  • server/claude-sdk.js

📝 Walkthrough

Walkthrough

The change propagates application session IDs into provider model resolution, prioritizes per-session overrides, filters conflicting Claude model environment variables, and updates command model selection to honor valid user overrides.

Changes

Session model override handling

Layer / File(s) Summary
Application session model lookup
server/modules/providers/services/provider-models.service.ts, server/modules/providers/tests/provider-models.service.ts, server/modules/websocket/services/chat-websocket.service.ts
Runtime options now carry the application session ID, which resolveResumeModel prefers for override lookup and validates through a new test.
Provider runtime propagation
server/claude-sdk.js, server/cursor-cli.js, server/openai-codex.js, server/opencode-cli.js
Provider entry points pass options.appSessionId into resume-model resolution.
Command and SDK model selection
server/routes/commands.js, server/claude-sdk.js
Command resolution checks valid session overrides first, while Claude SDK environment construction removes model-related variables before forwarding it.

Sequence Diagram(s)

sequenceDiagram
  participant ChatUI
  participant ChatWebSocket
  participant ProviderRuntime
  participant ProviderModelsService
  participant ActiveModelStore
  ChatUI->>ChatWebSocket: send message with sessionId
  ChatWebSocket->>ProviderRuntime: pass appSessionId
  ProviderRuntime->>ProviderModelsService: resolveResumeModel(..., appSessionId)
  ProviderModelsService->>ActiveModelStore: getChangedActiveModel(appSessionId)
  ActiveModelStore-->>ProviderModelsService: model override
  ProviderModelsService-->>ProviderRuntime: resolved model
Loading

Possibly related PRs

  • siteboon/claudecodeui#682: Both changes modify how server/claude-sdk.js builds the SDK subprocess environment and handles model-related environment inputs.

Suggested reviewers: viper151, blackmammoth

Poem

I’m a rabbit with a model to choose,
No session’s preference shall hide or lose.
App IDs hop through the runtime stream,
Overrides now follow their rightful dream.
Conflicting env vars nibble away—
The chosen model leads the way!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: resolving per-session model overrides via app session ID.
Linked Issues check ✅ Passed The changes address #981 by honoring Chat UI model changes through app-session-aware model resolution and runtime propagation.
Out of Scope Changes check ✅ Passed All changes support the stated fix, including session ID propagation, override lookup, tests, and SDK env cleanup.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Kennems and others added 2 commits July 11, 2026 15:18
resolveCommandModel called getCurrentActiveModel which reads from the
JSONL file, never checking session-model-changes.json where the UI
stores per-session model overrides via /active-model. Now checks the
stored overrides first before falling back to the provider default.

Fixes siteboon#981 (display part)

Co-Authored-By: Claude <noreply@anthropic.com>
The /active-model endpoint stores model changes keyed by app session ID,
but provider runtimes called resolveResumeModel with only the provider-
native session ID, so the stored override was never found.

- Update resolveResumeModel to accept optional appSessionId parameter
- Pass appSessionId from handleChatSend to all provider runtimes
- Strip ANTHROPIC_MODEL env vars so explicit model option takes precedence
- Add test verifying appSessionId-based model resolution

Fixes siteboon#981 (runtime part)

Co-Authored-By: Claude <noreply@anthropic.com>
@Kennems
Kennems force-pushed the fix/session-model-override branch from 1e1141b to 03c7f1c Compare July 11, 2026 07:18

@Sunjaieks Sunjaieks left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kennems
I also made a PR just for your reference: https://github.com/siteboon/claudecodeui/pull/998/changes

Following points are missed in your PR:

  1. https://github.com/siteboon/claudecodeui/pull/998/changes#diff-109e679f789e12dec8fb6884a19ca19526b4e4333fbb8c4d7e02040fda6f6983R146
    because in in claude's Jsonl file, field of model can be "<synthetic>"

2.847c9b7#diff-dda58f7c69e200b74287459fcd373aaf41a5ad0b25d31d91c8db2021a62b27e9R544
it will be better that new session will use the latest model which user selected from other session.

3.847c9b7#diff-109e679f789e12dec8fb6884a19ca19526b4e4333fbb8c4d7e02040fda6f6983R248
for highlighting

  1. maybe anything else?

I am new to this project, so maybe my PR also missed something. you can ask AI to analyze and merge the good point of each other.

@Sunjaieks

Copy link
Copy Markdown

@Kennems
I added your change point to my PR: #998

MacLeod92 added a commit to MacLeod92/claudecodeui that referenced this pull request Jul 16, 2026
…vider-native id

resolveResumeModel looked up a session's stored model override keyed by
the provider-native session id, but overrides are written keyed by the
app-facing session id — so a picked model would only actually apply
because the client also resends it on every message, not because the
override lookup succeeded. A reconnect or a second client on the same
session would silently miss it.

Credit to siteboon#996 (Kennems) for identifying this mismatch. This applies
the same fix but centralizes the appSessionId-or-sessionId fallback
inside resolveResumeModel itself, so the four provider runtimes
(claude-sdk, cursor-cli, openai-codex, opencode-cli) just pass both ids
through instead of each repeating the fallback logic.
MacLeod92 added a commit to MacLeod92/claudecodeui that referenced this pull request Jul 16, 2026
…vider-native id

resolveResumeModel looked up a session's stored model override keyed by
the provider-native session id, but overrides are written keyed by the
app-facing session id — so a picked model would only actually apply
because the client also resends it on every message, not because the
override lookup succeeded. A reconnect or a second client on the same
session would silently miss it.

Credit to siteboon#996 (Kennems) for identifying this mismatch. This applies
the same fix but centralizes the appSessionId-or-sessionId fallback
inside resolveResumeModel itself, so the four provider runtimes
(claude-sdk, cursor-cli, openai-codex, opencode-cli) just pass both ids
through instead of each repeating the fallback logic.

Also fixes the new GET /active-model endpoint, which read only the
transcript-resolved model and ignored a pending override — so a second
tab/device opened after a model change would display the stale model
instead of the pick. It now checks the stored override first, same
priority order siteboon#996 used for commands.js's resolveCommandModel.
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.

Can not change model from Chat UI

2 participants