Bug: Default model selection does not persist across page reloads
Summary
When a user selects a default model in Settings > Chat Preferences, the selection appears to save (no API errors), but after reloading the page the dropdown reverts to "No default (use first available)". The setting is not visually restored despite being stored in DynamoDB.
Environment
- Frontend: Angular v21, standalone components, Angular Signals
- Backend: FastAPI, DynamoDB (user settings table)
- Affected page:
/settings/chat — "Default model" dropdown
Steps to Reproduce
- Log in and navigate to Settings > Chat Preferences.
- Open the "Default model" dropdown.
- Select any model (e.g. "Claude Haiku 4.5 (Anthropic)").
- Observe: no error is shown, "Saving..." indicator appears and disappears.
- Reload the page (F5 or browser refresh).
- Observe: the dropdown shows "No default (use first available)" instead of the previously selected model.
Expected: The dropdown should show the model selected in step 3.
Actual: The dropdown resets to the empty/default option every time.
Root Cause Analysis
There are two distinct issues contributing to this bug — one on the frontend (display) and one on the backend (usage at chat time).
Issue 1: Frontend — <select> binding with Angular Signals and resource()
The chat preferences component uses Angular's resource() API to load settings:
// user-settings.service.ts
readonly settingsResource = resource({
loader: async () => this.fetchSettings(),
});
The component binds the dropdown value via a computed signal:
// chat-preferences-settings.page.ts
readonly currentDefaultModelId = computed(() => {
const settings = this.userSettingsService.settingsResource.value();
return settings?.defaultModelId ?? '';
});
And the template uses [value] binding on the <select>:
<select [value]="currentDefaultModelId()" (change)="onModelChange($event)">
<option value="">No default (use first available)</option>
@for (model of modelService.availableModels(); track model.id) {
<option [value]="model.modelId">{{ model.modelName }} ({{ model.providerName }})</option>
}
</select>
The problem is a race condition between two async data sources:
settingsResource loads the user's defaultModelId from the API.
modelService.availableModels() loads the model list from a separate API call.
If the settings resolve before the model list, the <select> tries to set its value to a modelId that doesn't have a matching <option> yet (because the @for loop hasn't rendered). The browser silently resets the <select> to the first option (""). When the models finally load and the options render, Angular doesn't re-apply the [value] binding because the computed signal value hasn't changed.
Additionally, [value] on a native <select> is a one-time DOM property set — it doesn't reactively re-sync when options are added later.
Issue 2: Backend — User's default model is never applied at chat time
When the user sends a message without explicitly selecting a model in the session, the frontend sends model_id: null to the backend. The chat stream endpoint in backend/src/apis/app_api/chat/routes.py creates the agent without looking up the user's saved default:
# chat/routes.py — current behavior
agent = get_agent(
session_id=request.session_id,
user_id=user_id,
enabled_tools=authorized_tools,
system_prompt=system_prompt,
# model_id is NOT passed — user's default is never loaded
)
The agent falls back to the hardcoded system default (us.anthropic.claude-haiku-4-5-20251001-v1:0) instead of the user's saved preference.
Proposed Solutions
Fix 1: Frontend — Ensure dropdown reflects persisted value after both data sources load
Option A — Use a computed that depends on both signals:
Create a combined readiness signal that only emits the defaultModelId once both the settings and the model list have loaded:
readonly currentDefaultModelId = computed(() => {
const settings = this.userSettingsService.settingsResource.value();
const models = this.modelService.availableModels();
if (!settings || models.length === 0) return '';
return settings.defaultModelId ?? '';
});
This ensures the value is only set after the <option> elements exist in the DOM.
Option B — Switch to reactive form control or ngModel:
Replace the native [value] binding with Angular's ngModel or a reactive FormControl, which handles async option population correctly:
<select [ngModel]="currentDefaultModelId()" (ngModelChange)="onModelChange($event)">
Option C — Force re-render after models load:
After the model list loads, trigger a change detection cycle or manually set the select element's value via ViewChild.
Recommended: Option A is the simplest and stays within the existing signal-based architecture.
Fix 2: Backend — Load user's default model when model_id is null
In the chat stream endpoint, when request.model_id is None, look up the user's saved defaultModelId from the settings table and pass it to the agent:
# chat/routes.py — proposed fix
model_id = request.model_id
provider = request.provider
if model_id is None:
user_settings = await user_settings_repo.get_settings(user_id)
saved_default = user_settings.get("defaultModelId")
if saved_default:
model_id = saved_default
# Optionally resolve provider from managed models table
agent = get_agent(
session_id=request.session_id,
user_id=user_id,
enabled_tools=authorized_tools,
system_prompt=system_prompt,
model_id=model_id,
provider=provider,
)
Fix 3: Frontend — Apply user's default model to session model selector
The ModelService.loadModels() method currently resolves the selected model with this priority:
- In-memory selection
sessionStorage (tab-scoped)
- Admin-configured default (
isDefault: true on the model)
- First available model
The user's personal default from the settings API is never consulted. Add it as priority 3 (after sessionStorage, before admin default):
// model.service.ts — proposed addition in loadModels()
} else if (enabledModels.length > 0) {
const savedModelId = this.getSavedModelId();
const savedModel = savedModelId ? enabledModels.find(m => m.modelId === savedModelId) : null;
if (savedModel) {
this._selectedModel.set(savedModel);
this.usingDefaultModel.set(false);
} else {
// NEW: Check user's persisted default from settings API
const userSettings = await firstValueFrom(
this.http.get<UserSettings>(userSettingsUrl)
);
const userDefault = userSettings?.defaultModelId
? enabledModels.find(m => m.modelId === userSettings.defaultModelId)
: null;
if (userDefault) {
this._selectedModel.set(userDefault);
this.usingDefaultModel.set(false);
} else {
// Fall back to admin default or first model
const defaultModel = enabledModels.find(m => m.isDefault);
this._selectedModel.set(defaultModel || enabledModels[0]);
this.usingDefaultModel.set(false);
}
}
}
Affected Files
| File |
Role |
frontend/.../chat-preferences-settings.page.ts |
Settings UI — dropdown binding (Fix 1) |
frontend/.../services/user-settings.service.ts |
Settings API client |
frontend/.../services/model/model.service.ts |
Model selection logic (Fix 3) |
backend/.../app_api/chat/routes.py |
Chat endpoint — agent creation (Fix 2) |
backend/.../shared/user_settings/repository.py |
DynamoDB settings read/write |
Priority
Medium — The feature silently fails (no errors in console or network), so users believe their preference was saved when it wasn't. This affects both the settings display and the actual model used during chat.
Bug: Default model selection does not persist across page reloads
Summary
When a user selects a default model in Settings > Chat Preferences, the selection appears to save (no API errors), but after reloading the page the dropdown reverts to "No default (use first available)". The setting is not visually restored despite being stored in DynamoDB.
Environment
/settings/chat— "Default model" dropdownSteps to Reproduce
Expected: The dropdown should show the model selected in step 3.
Actual: The dropdown resets to the empty/default option every time.
Root Cause Analysis
There are two distinct issues contributing to this bug — one on the frontend (display) and one on the backend (usage at chat time).
Issue 1: Frontend —
<select>binding with Angular Signals andresource()The chat preferences component uses Angular's
resource()API to load settings:The component binds the dropdown value via a computed signal:
And the template uses
[value]binding on the<select>:The problem is a race condition between two async data sources:
settingsResourceloads the user'sdefaultModelIdfrom the API.modelService.availableModels()loads the model list from a separate API call.If the settings resolve before the model list, the
<select>tries to set its value to amodelIdthat doesn't have a matching<option>yet (because the@forloop hasn't rendered). The browser silently resets the<select>to the first option (""). When the models finally load and the options render, Angular doesn't re-apply the[value]binding because the computed signal value hasn't changed.Additionally,
[value]on a native<select>is a one-time DOM property set — it doesn't reactively re-sync when options are added later.Issue 2: Backend — User's default model is never applied at chat time
When the user sends a message without explicitly selecting a model in the session, the frontend sends
model_id: nullto the backend. The chat stream endpoint inbackend/src/apis/app_api/chat/routes.pycreates the agent without looking up the user's saved default:The agent falls back to the hardcoded system default (
us.anthropic.claude-haiku-4-5-20251001-v1:0) instead of the user's saved preference.Proposed Solutions
Fix 1: Frontend — Ensure dropdown reflects persisted value after both data sources load
Option A — Use a computed that depends on both signals:
Create a combined readiness signal that only emits the
defaultModelIdonce both the settings and the model list have loaded:This ensures the value is only set after the
<option>elements exist in the DOM.Option B — Switch to reactive form control or
ngModel:Replace the native
[value]binding with Angular'sngModelor a reactiveFormControl, which handles async option population correctly:Option C — Force re-render after models load:
After the model list loads, trigger a change detection cycle or manually set the select element's value via
ViewChild.Recommended: Option A is the simplest and stays within the existing signal-based architecture.
Fix 2: Backend — Load user's default model when
model_idis nullIn the chat stream endpoint, when
request.model_idisNone, look up the user's saveddefaultModelIdfrom the settings table and pass it to the agent:Fix 3: Frontend — Apply user's default model to session model selector
The
ModelService.loadModels()method currently resolves the selected model with this priority:sessionStorage(tab-scoped)isDefault: trueon the model)The user's personal default from the settings API is never consulted. Add it as priority 3 (after sessionStorage, before admin default):
Affected Files
frontend/.../chat-preferences-settings.page.tsfrontend/.../services/user-settings.service.tsfrontend/.../services/model/model.service.tsbackend/.../app_api/chat/routes.pybackend/.../shared/user_settings/repository.pyPriority
Medium — The feature silently fails (no errors in console or network), so users believe their preference was saved when it wasn't. This affects both the settings display and the actual model used during chat.