Skip to content

fix(tui): retry session model hydration until it applies - #38941

Open
iceteaSA wants to merge 1 commit into
anomalyco:devfrom
iceteaSA:model-hydration-race
Open

fix(tui): retry session model hydration until it applies#38941
iceteaSA wants to merge 1 commit into
anomalyco:devfrom
iceteaSA:model-hydration-race

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Jul 26, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #38940

Type of change

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

What does this PR do?

Under OPENCODE_FAST_BOOT, opening a session whose last user message used a non-default model leaves the composer on the default. The session's selection is dropped, and because Anthropic caching keys on the model, the next turn re-uploads the whole context.

What was wrong. packages/tui/src/component/prompt/index.tsx:310-332 set the syncedSessionID latch before running the two setters that do the hydration:

syncedSessionID = sessionID                                    // latched here
const isPrimaryAgent = local.agent.list().some((x) => x.name === msg.agent)
if (msg.agent && isPrimaryAgent) {                             // bail 1
  if (!args.agent) local.agent.set(msg.agent)
  if (msg.model) local.model.set(msg.model)                    // bail 2

Both setters can silently no-op on data that has not loaded yet. An empty agent list makes isPrimaryAgent false and skips the whole block; local.model.set() returns early when isModelValid() finds no provider, because that reads sync.data.provider (context/local.tsx:64-67), still empty while providers load. The latch was already set, so the effect never re-entered and the session kept fallbackModel — CLI arg, then config model, then recent[0], then provider default. recent[0] is persisted, so you usually land on your previously selected model rather than an obviously wrong one, which is why it reads as intermittent.

Normal boot never hits this. SyncProvider holds the app behind init.ready (context/helper.tsx:15) and ready is false while loading unless fast-boot is on (context/sync.tsx:558), so Prompt does not mount until the catalogs are in. Fast-boot removes that gate, which is what exposes the race.

The change. Latch after hydration instead of before, and return early while sync.status === "loading" and the needed catalog entry is still missing. Both reads stay tracked, so the effect re-runs when the data arrives and settles once it applies.

Behaviour after load is unchanged: still one-shot per session, so later messages do not re-hydrate and an unsent local model choice is not overwritten.

How did you verify your code works?

packages/tui/test/cli/tui/model-hydration-race.test.tsx mounts the real contexts with skipInitialLoading: true and resolves /api/agent after the session's messages are already available.

On unfixed source:

Expected: beta/repro
Received: alpha/model-a

At the moment the effect runs, the message is present, local.agent.list() is [], and sync.data.provider is [] — bail 1.

Reverting the source change turns the test red again and restoring it turns it green, so the test observes the fix rather than passing incidentally.

Running the same scenario with skipInitialLoading: false passes on unfixed source. That is how the fast-boot scoping was established — the first draft of this fix assumed the race applied to normal boot as well, and the test disproved it.

Gates from packages/tui: bun test 192 pass / 1 skip / 0 fail; bun typecheck exit 0.

Not covered. This does not address the other reported ways a session reverts to the default model — #38165, #38770, #28735. Those come through the server-side prompt path, where an injected prompt without an explicit model resolves to the agent's configured default (packages/opencode/src/session/prompt.ts:646). #35195 targets that path.

Screenshots / recordings

Not a UI change — behaviour only, covered by the test above.

Checklist

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

@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Jul 26, 2026
@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Jul 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

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.

TUI: session model hydration is lost under OPENCODE_FAST_BOOT

1 participant