Conversation
Picking a model wrote it only to the live session, so the next launch fell
back to whatever the provider listed first. The choice is now stored under
`model:{provider}` and read back by `resolve` when no config override is set,
so each provider reopens on the model it was left on.
The stored id comes from the database's active provider rather than from the
provider's display label, so renaming a label cannot silently orphan the key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Important
The remembered model is keyed off a different provider id on write than on read, so the fix does not restore the model in the default flow — the exact bug this PR targets. One-line fix; details inline.
Reviewed changes
- Read the remembered model in
catalog.resolve— when no config override is set, the model comes from thesettingtable undermodel:{entry.id}, whereentryis the active provider afterfindOrDefaultnormalization (src/provider/catalog.zig:144). - Persist the model pick in the TUI —
switchModelnow stores each choice undermodel:{activeProvider}via a newrememberModelChoicehelper (src/tui/commands.zig:468). - New resolution test — covers restore, no cross-provider inheritance, and config override winning; assertions are exact strings, so the test can fail (src/provider/catalog.zig:259).
openai-compatible/glm-5.3-flash:cloud | 𝕏
The write path read the provider straight out of the database, which is empty until something has connected, so a fresh install stored the pick under `model:` while `resolve` read `model:ollama`. The model was never restored in the default flow, and a pinned `SYNTH_PROVIDER` wrote its pick under whichever provider the database happened to name. `Model.provider_id` now carries the id `resolve` settled on, set at launch and updated when a provider connects, so both paths key off the same normalized id by construction rather than by two call sites agreeing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Good catch, and it was right: the fix did not work in the flow it targets. Fixed in I traced it before fixing. The Answering your open question: yes, a pin should move where the pick is stored. A pinned provider's remembered model should be reachable, and On the shape of the fix. Tests. Worth noting for your next pass: the sole remaining gap is that nothing test-covers the TUI write path itself, only the invariant it has to satisfy. |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — re-reviewed the two new commits (752d841..0ab75f3) against the prior pullfrog review. The key-mismatch finding is fixed cleanly:
- Introduced
Model.provider_id— holds the normalized catalog id, defaulting tocatalog.default_id, seeded fromcatalog.resolve'sentry.idat construction (src/main.zig:204). - Rewired
rememberModelChoiceonto it — the write path now stores undermodel:{self.provider_id}instead of the rawdb.activeProvider()value, so write and read key off the same id (src/tui/commands.zig:478). - Re-synced the id on provider switch —
connectProviderupdatesself.provider_id = entry.idafter a successful connect, and it is the only production path that swaps the TUI's provider, so the id cannot go stale (src/tui/commands.zig:327). - Added a fresh-database test — pins the default flow end to end: a fresh DB resolves to
default_id, a pick stored under that id is read back, andprovider_overridemoves both the key and the id (src/provider/catalog.zig:290). Assertions are exact strings, so the test can fail.
The provider_override divergence raised in the prior review is resolved too: the write key now follows provider_id, which carries the same normalized id resolve settles on, whether that came from the database row or the config pin. Resume paths (restoreModel) never swap providers, so no other sync site is missing.
Author-flagged follow-ups (stale remembered model vs the provider's current catalog, picker sorting/filtering) remain explicitly out of scope.
openai-compatible/glm-5.3-flash:cloud | 𝕏
`activeProviderId` already normalizes the stored provider through `findOrDefault`, which is what the read path in `catalog.resolve` does, so the write path can just call it. The cached `Model.provider_id` this replaces had to be updated by hand wherever the provider changed, and stale cached state is a worse failure than a database read. A pinned `SYNTH_PROVIDER` still stores the pick under the database's provider rather than the pin. That gap predates this change and is left documented rather than fixed here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Simplified in
Both are now gone. The write path is one call to the existing helper. I also want to retract the "agree by construction" claim I made in my previous comment. The 508 tests, |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — re-reviewed the one new commit (0ab75f3..90aff09) against the prior pullfrog review:
- Dropped the cached
Model.provider_idfield —rememberModelChoicenow derives the provider id from the database through the existingactiveProviderIdhelper at write time (src/tui/commands.zig:477), so write and read key off the samefindOrDefault-normalized id with no field to keep in sync. - Removed the sync sites that existed only for the cache — the
Model{}seeding inmain.zigand the re-assignment inconnectProviderare gone;connectProvideralready writes the DB row beforeswitchTocallsswitchModel, so the write path always sees the fresh id. - Updated the fresh-database test — the
provider_overridecase now documents the write-side gap in prose, keeping the assertion that a pinned read comes back empty.
Verified statically (no Zig toolchain here): no remaining references to the removed field; the arena-scoped key is copied by setSetting's exec before the arena frees; activeProviderId's no-database default (catalog.default_id) is unreachable from rememberModelChoice, which returns first when there is no database. The pin-flow note below matches what the test comment now documents: with config.provider_override set to a provider other than the database row, a pick made via bare /model (no reconnect) is stored under the row's id rather than the pin's, so it is not read back on the next pinned launch. Picks made through the model picker (switchTo) reconnect first, update the row, and key correctly.
openai-compatible/glm-5.3-flash:cloud | 𝕏
aw1875
left a comment
There was a problem hiding this comment.
nothing blocking but just a few notes
| var configured_model = config.model_override; | ||
| if (configured_model.len == 0) { | ||
| const model_key = try std.fmt.allocPrint(arena, "model:{s}", .{entry.id}); | ||
| configured_model = try db.setting(arena, model_key); |
There was a problem hiding this comment.
db.setting returns an empty string for missing and empty, so a corrupted write silently re-falls back every launch
| self.loop.provider.context_limit = current.context_limit; | ||
| self.loop.provider.vision = current.vision; | ||
| try self.loop.setModel(current.model); | ||
| try rememberModelChoice(self, current.model); |
There was a problem hiding this comment.
if this fails, there's no error shown, so the next launch just reopens on the old model

Summary
Fixes "if I close it and reopen, it shows astra again".
model:{provider}, andcatalog.resolvereads it back when no config override is set.config.model_overrideand the environment still win, so nothing about existing precedence changes.Extracted from #23, which is being closed. The write path calls the existing
activeProviderId(commands.zig:414), which normalizes the stored provider throughfindOrDefaultexactly the waycatalog.resolvedoes, so both paths key off the same id.An earlier revision of this PR hand-rolled that lookup and dropped the normalization, which broke the default flow. A later one over-corrected by caching the id on
Model, which had to be updated by hand wherever the provider changed. Both are gone as of90aff09.Needs no new database schema.
setting,setSettingandactiveProviderall already exist.Test plan
zig build test(507 passed, 5 skipped, up from 506 on master)zig buildcleanzig fmt --checkcleanDeviations from the ticket text
No ticket. The storage key matches #23 (
model:{provider}), so a selection stored by that branch is still read correctly.Out of scope (flagged for follow-up)
SYNTH_PROVIDERstill stores the pick under the database's own provider rather than under the pin, so a pick made under a pin is not read back. That gap predates this change;activeProviderIdcannot seeConfig, and threading it in is a larger shape than this fix.ensureModelcurrently means three different things across the three providers: ollama silently substitutes the first model and never rewrites the setting, so it re-substitutes on every launch; openai never validates a stored name at all. Worth settling that contract in its own change.🤖 Generated with Claude Code