Skip to content

fix(tui): store a copy of the selected model, not the caller's object - #1365

Merged
ralphstodomingo merged 2 commits into
mainfrom
fix/tui-model-store-shared-reference
Sep 24, 2026
Merged

ralphstodomingo merged 2 commits into
mainfrom
fix/tui-model-store-shared-reference

Conversation

@ralphstodomingo

@ralphstodomingo ralphstodomingo commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #1364

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

selectModel() in packages/tui/src/context/local.tsx stored the model object it was given with setModelStore("model", agent, model). A Solid store keeps the first object set at a path by reference, and later sets at that path merge their fields into that same object (updatePath → mergeStoreNode).

Opening a conversation calls restoreSession(msg.model), passing the last user message's recorded model, an object owned by the sync store. When that is the first model stored for the agent in the launch, the model store and the message record share one object. Opening another conversation then merges its model into the first conversation's record, so returning to the first conversation restores the second one's model.

The fix stores a copy ({ providerID, modelID }), so the model store never holds or writes into an object owned by another store. It's a three-line change.

How did you verify your code works?

  • New test test/context/model-store-aliasing.test.tsx. It mounts the real LocalProvider and SyncProvider and feeds two conversations' user messages through message.updated events. It then calls restoreSession() with each conversation's recorded model, as the prompt does, and returns to the first. The test asserts the first conversation's record and the selection on return. It fails on main (the record reads back as the second conversation's model) and passes with this change.

  • In the real TUI, from source, with two conversations recorded on different providers:

    Step Recorded model Selected in the prompt, main Selected, this PR
    Open A Acme Large Acme Large Acme Large
    Open B Altimate Base Altimate Base Altimate Base
    Back to A Acme Large Altimate Base Acme Large
  • TUI suite: 339 pass. dialog-scan-gate > "pressing y chooses scan" fails identically on main. Typecheck and the marker/branding guards are clean.

Screenshots / recordings

Text captures are in the table above; the change has no visual component.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_016x3nbZU5Vb6HnSTqE7vELm


Summary by cubic

Fixes cross-conversation model aliasing so returning to a previously opened conversation restores its own model instead of a later one's.

Bug Fixes

  • selectModel() now stores a copy of the model rather than the caller's object; Solid stores keep the first object set at a path by reference and merge later sets into it, so the old code wrote later selections into another conversation's recorded message.
  • Adds a regression test that reproduces the aliasing by restoring two conversations' recorded models in sequence.

Refactors

  • Shares the model-selection test harness and fixtures between the aliasing and stale-Zen cycle tests.

Written for commit 68fcbd1. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Switching between conversations no longer changes the model information recorded in their messages.

`selectModel()` stored the given model object in the model store by
reference. A Solid store keeps the first object set at a path and merges
later sets into it, so when that first object was a conversation's
recorded model from the sync store, opening a second conversation wrote
its model into the first conversation's record, and returning to the
first selected the second's model. Store a copy instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016x3nbZU5Vb6HnSTqE7vELm
@ralphstodomingo ralphstodomingo self-assigned this Sep 23, 2026
@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: bcf9e533-9377-4233-b5fb-2c086d6beda1

📥 Commits

Reviewing files that changed from the base of the PR and between f85c3be and 68fcbd1.

📒 Files selected for processing (3)
  • packages/tui/test/context/model-store-aliasing.test.tsx
  • packages/tui/test/context/stale-zen-cycle.test.tsx
  • packages/tui/test/fixture/local-model.tsx

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

Model selection now stores a copy of the selected model object. A shared test fixture supports tests that restore models across conversations and cycle through model selections.

Changes

Conversation model restoration

Layer / File(s) Summary
Copy selected models and test restoration
packages/tui/src/context/local.tsx, packages/tui/test/fixture/local-model.tsx, packages/tui/test/context/model-store-aliasing.test.tsx, packages/tui/test/context/stale-zen-cycle.test.tsx
selectModel stores a new object with the selected provider and model IDs. A shared fixture sets up the local and sync providers, mock models, and test state. Both tests now use this fixture.

Estimated code review effort: 2 (Simple) | ~12 minutes

Merge Risk: ⚪ Minimal · up to 68fcb

The model-selection change and test refactor appear ready to merge after normal checks; no actionable issue remains identified.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: storing a copy of the selected model instead of the caller's object.
Description check ✅ Passed The description includes the issue reference, change type, root cause, fix rationale, verification details, screenshots note, and completed checklist. The added test and reported results directly supp…
Linked Issues check ✅ Passed Issue #1364 requires correct model restoration when the TUI switches between conversations. packages/tui/src/context/local.tsx now stores a new object containing providerID and modelID in `selec…
Out of Scope Changes check ✅ Passed The production change directly addresses the cross-conversation model aliasing in issue #1364. The shared local-model.tsx fixture and test refactor support the model-selection regression tests. No u…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

A rabbit checks the model store with care,
A copied choice stays safely there.
Two chats switch models, then switch back,
Shared test fixtures keep tests on track.
The rabbit hops, content with the change.

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

@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review

Please try to falsify these claims with a concrete sequence:

  • C1: Opening conversations in any order never changes another conversation's recorded model in the sync store.
  • C2: Returning to a conversation selects the model it was recorded with, unless the user picks another model.
  • C3: No other model-store write keeps a reference to an object owned by another store.

Please skip style nits.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: f85c3be6b7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-23T21:39:19.043718Z 68fcbd1 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ralphstodomingo
ralphstodomingo marked this pull request as ready for review September 23, 2026 21:27

@claude claude Bot 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.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@kilo-code-bot

kilo-code-bot Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • packages/tui/src/context/local.tsx
  • packages/tui/test/context/model-store-aliasing.test.tsx
  • packages/tui/test/context/stale-zen-cycle.test.tsx
Previous Review Summary (commit f85c3be)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit f85c3be)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • packages/tui/src/context/local.tsx
  • packages/tui/test/context/model-store-aliasing.test.tsx
  • packages/tui/test/context/stale-zen-cycle.test.tsx

Reviewed by gpt-sol-latest · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/tui/test/context/model-store-aliasing.test.tsx Outdated
Move the LocalProvider and SyncProvider mount, the model fixtures and
`waitUntil` into `test/fixture/local-model.tsx`, so the cycling tests and
the conversation-switching test use one copy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016x3nbZU5Vb6HnSTqE7vELm
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review

Scoped review of 68fcbd1, the delta from f85c3be: a test-only refactor that moves the TUI model-selection test harness into test/fixture/local-model.tsx. Please check that both tests still exercise what they claim (C1–C3 from the earlier request). Please skip style nits.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 68fcbd164a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@sahrizvi sahrizvi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. The root cause is right, and the copy sits in selectModel, which every selection path (picker, favorite cycle, session restore) goes through. I confirmed the new regression test fails with only src/context/local.tsx reverted: A's record reads back as altimate-free/altimate-base. It passes with the fix, and so does the stale-Zen suite.

A few optional follow-ups, none of them blocking:

  1. Strengthen the regression test (test/context/model-store-aliasing.test.tsx:42-48). It checks A's record and the final selection, but not B's record, and not the selection right after opening B. Adding these would cover both sides:
    local.model.restoreSession(recordedB().model)
    expect(local.model.current()).toMatchObject(BASE)
    // ...
    expect({ ...recordedB().model }).toMatchObject(BASE)
  2. restoreSession returns the caller's object (src/context/local.tsx:1096-1103). In the non-Zen branch, resolved is the sync store's message record. This is not a bug today: the only caller (component/prompt/index.tsx:403) only reads fields. Returning { providerID: model.providerID, modelID: model.modelID } would keep the API from handing out a store-owned object.
  3. Nit: model-store-aliasing.test.tsx:34 repeats the local.model.ready wait that mount() already does.

@ralphstodomingo
ralphstodomingo merged commit 10fa461 into main Sep 24, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TUI: returning to a conversation selects the model of the conversation opened before it

2 participants