Skip to content

fix(settings): preserve self-hosted language models on restart - #2790

Open
Jrjy3 wants to merge 4 commits into
OpenWhispr:mainfrom
Jrjy3:fix/persist-self-hosted-models
Open

Jrjy3 wants to merge 4 commits into
OpenWhispr:mainfrom
Jrjy3:fix/persist-self-hosted-models

Conversation

@Jrjy3

@Jrjy3 Jrjy3 commented Oct 19, 2026

Copy link
Copy Markdown

Fixes #2789

Problem

A Self-Hosted model selection doesn't survive a restart. Every time the app starts, Self-Hosted stays selected but its model changes to a cloud provider's default, for example openai/gpt-oss-120b, and the change is saved. Requests then ask the self-hosted server to load that model, which the user's machine may not be able to run, and the user has to select their model again after every launch.

This happens whenever the scope's saved provider is a cloud provider, which is always the case for Chat: its provider defaults to Groq, so a Self-Hosted Chat model is reset on every restart even if the user never picked a cloud provider. Other scopes hit it as soon as they have used a cloud provider before switching to Self-Hosted.

Root cause

Every mode stores its model in the scope's one <scope>Model key, but <scope>Provider keeps the last cloud provider after switching to Self-Hosted. All three cloud-model reconciliation paths check the model against that provider's catalog without looking at the scope's mode:

  • the one-time retired-model migration at startup (migrateRetiredCloudModels)
  • startup registry reconciliation (reconcileRetiredCloudModelSelections)
  • the live Tinfoil catalog check (reconcileSelectedModels)

A self-hosted model id isn't in the Groq or Tinfoil catalog, so it gets "repaired."

Fix

Each path now skips a scope unless its mode uses a cloud catalog (usesCloudModelCatalog: providers or openwhispr). Self-Hosted, Local and Enterprise selections are left as they are. Cloud modes keep the existing retirement handling, including profiles with no saved mode, which load as openwhispr. The screen-context vision override is always providers, so it is still reconciled.

Tests

  • New test/helpers/selfHostedModelPersistence.test.js: Dictation Cleanup, Voice Assistant, Note Formatting, Chat and Translation, in all five modes, across all three paths. It checks the store and persisted storage, and that a Tinfoil switch notice appears only when a cloud selection is replaced. The 9 Self-Hosted, Local and Enterprise cases fail without the fix.
  • Existing suites still pass: reasoningModelMemory, retiredCloudModels, settingsStoreRetiredGroqModels, tinfoilDefaultModel (54 tests total, Node 24.22).
  • ESLint, Prettier and the desktop type check pass on the changed files.
  • Manual (Windows, dev build): set Chat to Self-Hosted with a custom model name, quit from the tray and relaunch. The model name persists; on main it comes back as openai/gpt-oss-120b.

Known limitations

A selection already overwritten by an earlier launch isn't restored. It has to be picked again once, and from then on it stays.

Jrjy3 and others added 2 commits October 19, 2026 11:23
Name the providers/openwhispr check once (usesCloudModelCatalog) for the
three reconciliation paths, correct the unset-mode comment, and split the
regression test into one case per path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Chadpiha
Chadpiha self-requested a review October 19, 2026 23:52
…ders

Restarts no longer repair a Self-Hosted scope's model, so a switch back to
Providers kept that id under the scope's old cloud provider and sent it to
Groq (for example) until the next restart. The mode switch now clears a
model the kept provider doesn't serve, so the picker asks for one.

Also covers the reported path in the regression test: Chat in Self-Hosted
with its provider left at the Groq default, through initializeSettings().

@Chadpiha Chadpiha 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.

Thanks @Jrjy3, great catch and a clean, well-scoped fix. Approving.

I traced all three repair paths end to end. Mode values match InferenceMode and are stored as plain strings. The startup filter runs after every mode migration. Profiles with no saved mode still load as openwhispr and get repaired, and the vision override (hard-coded to providers) is still reconciled. Your new test fails 9 cases on main and passes on the branch, and the related settings and model suites all pass.

I pushed one follow-up commit (60af471):

  • Switching back to Providers: restarts no longer overwrite a Self-Hosted model, so switching a scope back to Providers kept that model id under the old cloud provider (e.g. Groq) until the next restart. The mode switch now clears a model the kept provider doesn't serve (modeSwitchPatch in src/utils/llmModeSwitch.ts), so the picker asks for one.
  • Test of the reported path: Chat in Self-Hosted with its provider left at the Groq default, run through initializeSettings(), plus a Providers check that the repair still runs.

Not blocking, for later:

  • The startup sweep reads the mode as stored, while the store maps unknown values to openwhispr, so an unrecognised stored mode skips the one-time sweep.
  • The one-time sweep marks itself done even for the scopes it skipped.

…ed-models

# Conflicts:
#	src/components/settings/InferenceConfigEditor.tsx

This branch has not been deployed

No deployments
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.

Self-Hosted model selection changes to openai/gpt-oss-120b on every restart

2 participants