release: v6.8.6 - #55
Conversation
Plan-aware default model: free plans default to the catalog's freePlan entry, paid plans to the first catalog entry (index 0).
There was a problem hiding this comment.
🧪 PR Review is completed: Plan-aware default model resolution is well-structured (good guards against overwriting explicit user picks and against empty catalogs), but the headless path silently treats a failed profile fetch as a free plan, and the TUI path persists the auto-resolved default making it indistinguishable from an explicit user pick on future plan changes. Reviewed src/api/models.ts: no issues found. package.json: version bump only, no issues.
Skipped files
CHANGELOG.md: Skipped file patternpackage-lock.json: Skipped file pattern
⬇️ Low Priority Suggestions (2)
src/headless.ts (1 suggestion)
Location:
src/headless.ts(Lines 44-48)🟡 Logic Error / Error Handling
Issue:
fetchProfile(token).catch(() => null)swallows failures, and anullprofile resolves toplan === undefined, whichisFreePlan()treats as the free tier. A transient profile-fetch failure (network blip, 5xx) silently downgrades a paid user to the free-plan default model for the entire run — the opposite degradation of the existing gating block below, where an unhandledfetchProfilerejection fails loudly. Additionally, when the resolved default is a gated model (Lumen/Eido/400k),fetchProfileis fetched twice in the same run.Fix: Only re-resolve the default when the profile actually loaded; on failure keep the static default (
DEFAULT_MODEL_ID), which is the documented fallback.Impact: Paid users no longer get silently switched to the free model on transient errors; behavior matches the documented fallback semantics.
- if (token && !requestedModel && settings.model === DEFAULT_MODEL_ID) { - const profile = await fetchProfile(token).catch(() => null) - const plan = profile?.plan ?? profile?.tieredUsage?.plan - const preferred = getDefaultModelId(plan) - if (preferred !== settings.model) settings.model = preferred + if (token && !requestedModel && settings.model === DEFAULT_MODEL_ID) { + const profile = await fetchProfile(token).catch(() => null) + if (profile) { + const plan = profile.plan ?? profile.tieredUsage?.plan + const preferred = getDefaultModelId(plan) + if (preferred !== settings.model) settings.model = preferred + } + }
src/ui/App.tsx (1 suggestion)
Location:
src/ui/App.tsx(Lines 951-956)🟡 Business Logic Impact (needs discussion)
Issue: The auto-resolved plan-aware default is applied via
switchModel, which callssaveSettings— persisting it to config.json. After the first run,settings.model !== DEFAULT_MODEL_ID, so the guard at line 953 never fires again: the auto-selection becomes permanently indistinguishable from an explicit user pick. Consequences: (1) a free user who upgrades to pro stays on the free model forever (until manually changed), and vice versa a downgraded user keeps the paid default; (2) inconsistency with the headless path, which resolves per-run without persisting. The comment says "an explicit user pick is never overwritten" — correct — but the code can't tell an auto-pick from a user pick after persistence.Fix (architectural, beyond a minimal patch): Persist a marker distinguishing auto-resolved selections (e.g. a
modelSource: "auto" | "user"field in settings, or skipsaveSettingsfor the silent auto-switch and re-resolve on every startup while the marker is absent). Worth deciding intentionally before release since it affects every user's default after a plan change.- useEffect(() => { - if (!catalogReady || !planLoaded) return; - if (settings.model !== DEFAULT_MODEL_ID) return; - const preferred = getDefaultModelId(activePlan); - if (preferred !== settings.model) switchModel(preferred, { silent: true }); - }, [activePlan, catalogReady, planLoaded, settings.model, switchModel]); +
Summary
Release v6.8.6.
Fixed
DEFAULT_MODEL_ID: free accounts default to the catalog entry the backend flagsfreePlan, every other plan to the first catalog entry (index 0, ordered by the catalog'ssortOrder).fetchDynamicModelsrecords the catalog order and each model'sfreePlanflag, and the newgetDefaultModelId(plan)helper resolves the default. The TUI applies it once the catalog and the account plan have both loaded — only while the selection is still the untouched default, so an explicit pick is never overwritten — and headless mode applies it when no--model/MATTERAI_MODELwas requested.Verification
npm run typecheckandnpm run buildpass.After merge: tag
v6.8.6onmainto trigger the npm publish workflow.