fix: handle None providers config + tighten custom-provider model guards - #3967
fix: handle None providers config + tighten custom-provider model guards#3967lidi1011 wants to merge 2 commits into
Conversation
- agent_sessions: lower CLI_MIN_UNTITLED_USER_MESSAGE_COUNT 2→1
so single-message CLI sessions appear in sidebar
- config / onboarding / providers / routes: replace cfg.get("providers", {})
with (cfg.get("providers") or {}) everywhere. When the config key exists
but holds None (broken/malformed config), dict.get() returns None instead
of the default {}, causing downstream .get() calls to crash.
- config: don't discard "custom" provider when auto-detected models
are present (add 'and not auto_detected_models' guard)
- config: scope the auto_detected_models fallback to custom/local PIDs
only (elif pid in ("custom", "local") and auto_detected_models:),
preventing non-custom providers from inheriting the global fallback
|
| Filename | Overview |
|---|---|
| static/panels.js | Contains an unresolved Git merge conflict (lines 5650–5659) that will cause a JS syntax error at runtime, breaking profile-switching logic entirely. |
| api/config.py | All cfg.get("providers", {}) calls replaced with (cfg.get("providers") or {}); custom-provider discard guard now also checks auto_detected_models; auto_detected_models fallback scoped to custom/local PIDs only. |
| api/providers.py | Five cfg.get("providers", {}) call sites updated to (cfg.get("providers") or {}); logic is otherwise unchanged. |
| api/routes.py | Two cfg.get("providers", {}) call sites updated to (cfg.get("providers") or {}); no behavioral change beyond None-safety. |
| api/onboarding.py | Single cfg.get("providers", {}) call updated to (cfg.get("providers") or {}); straightforward None-safety fix. |
| api/agent_sessions.py | CLI_MIN_UNTITLED_USER_MESSAGE_COUNT lowered from 2 to 1 so single-message CLI sessions appear in the sidebar. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["switchToProfile(name)"] --> B{Has messages?}
B -- Yes --> C["newSession(false, opts)\nsyncTopbar()\nrenderSessionList()"]
B -- No --> D{Session stale\nfor new profile?}
D -- Yes --> E["newSession(false)"]
E --> F{_profileDefaultWorkspace\n& S.session?}
F -- Yes --> G["POST /api/session/update\n(workspace, model, provider)"]
F -- No --> H
G --> H["renderSessionList()\nsyncTopbar()"]
D -- No --> H
H --> I["⚠️ MERGE CONFLICT\n<<<<<<< Updated upstream\nloadDir('.') with await+mode check\n=======\nloadDir('.') fire-and-forget\n>>>>>>> Stashed changes"]
I --> J["showToast(profile_switched)"]
Reviews (2): Last reviewed commit: "fix(profiles): clear stale cross-profile..." | Re-trigger Greptile
…esquena#2535) When switching profiles without sending a message first, the old profile's session could remain active, causing subsequent workspace switches and sends to operate on the wrong profile. Now detect a stale session (profile mismatch) after switch and start a fresh session, applying the profile's default workspace.
| <<<<<<< Updated upstream | ||
| // Refresh workspace file tree so the right panel shows the new | ||
| // profile's workspace, not the previous one (#1214). | ||
| if (S.session && S.session.workspace) { | ||
| const dirLoad = loadDir('.'); | ||
| if (typeof _workspacePanelMode !== 'undefined' && _workspacePanelMode !== 'closed') await dirLoad; | ||
| } | ||
| ======= | ||
| if (S.session && S.session.workspace) loadDir('.'); | ||
| >>>>>>> Stashed changes |
There was a problem hiding this comment.
The file contains raw conflict markers (<<<<<<< Updated upstream, =======, >>>>>>> Stashed changes) at lines 5650–5659. When a browser parses this file, it will throw a syntax error the moment it reaches <<<<<<< Updated upstream, breaking the entire switchToProfile function and everything that follows. The two conflicting versions differ on whether loadDir('.') is awaited conditionally (the upstream version, which also respects _workspacePanelMode) or fired-and-forgotten (the stashed version). One of these must be chosen and the conflict markers removed before merging.
SummaryI pulled the branch into a read-only worktree and diffed Code reference — the blocker
syncTopbar();
<<<<<<< Updated upstream
// Refresh workspace file tree so the right panel shows the new
// profile's workspace, not the previous one (#1214).
if (S.session && S.session.workspace) {
const dirLoad = loadDir('.');
if (typeof _workspacePanelMode !== 'undefined' && _workspacePanelMode !== 'closed') await dirLoad;
}
=======
if (S.session && S.session.workspace) loadDir('.');
>>>>>>> Stashed changes
showToast(t('profile_switched', name));The "Updated upstream" side is the correct one — it matches the #1214 await-aware refresh already on The
|
|
Thanks @lidi1011 — salvaging the best part of this into a fresh, focused PR rather than rebasing the bundle. The standout fix here is the None-providers hardening: The other changes in this bundle we're intentionally dropping: the Closing in favor of the focused salvage PR — appreciate catching the empty-config crash. |
…(salvage of #3967) A config.yaml with an explicit empty/null `providers:` key parses to None in PyYAML. `cfg.get("providers", {})` only returns the {} default when the key is *absent*, so an explicit null yields None instead of an empty mapping. master already guards each read with `isinstance(providers_cfg, dict)`, but the value fed to that guard is still a footgun: the natural chained form `cfg.get("providers", {}).get(...)` (already fixed once in api/config.py) crashes with AttributeError on None. Harden every providers-key read in the three remaining files to `cfg.get("providers") or {}` so an explicit null degrades to an empty dict at the source, matching the existing api/config.py convention. Sites hardened: api/onboarding.py:578 (_provider_api_key_present) api/providers.py:1051 (_provider_has_shadowed_codex_oauth_value) api/providers.py:1219 (_provider_has_key) api/providers.py:1265 (_get_provider_api_key) api/providers.py:2348 (get_providers catalog) api/providers.py:2781 (_clean_provider_key_from_config) api/routes.py:5045 (_context_length_config_api_key_for_provider) api/routes.py:5110 (_context_length_lookup_inputs_for_model) api/routes.py:15916 (_handle_live_models) Adds tests/test_none_providers_config_guard.py: per-file source-form pins (fail on master / on any single-file revert) plus behavioural checks driving the real functions with `providers: None` to prove no crash and parity with the empty-mapping config. Updates the existing test_issue3717 routes-string pin to match the hardened form. This is a focused salvage of grab-bag PR #3967 -- only the None-providers hardening is taken; the PR's unrelated changes (account-usage worker-pool refactor, function removals) are intentionally dropped. Co-authored-by: lidi1011 <lidi1011@users.noreply.github.com>
…(salvage of #3967) A config.yaml with an explicit empty/null `providers:` key parses to None in PyYAML. `cfg.get("providers", {})` only returns the {} default when the key is *absent*, so an explicit null yields None instead of an empty mapping. master already guards each read with `isinstance(providers_cfg, dict)`, but the value fed to that guard is still a footgun: the natural chained form `cfg.get("providers", {}).get(...)` (already fixed once in api/config.py) crashes with AttributeError on None. Harden every providers-key read in the three remaining files to `cfg.get("providers") or {}` so an explicit null degrades to an empty dict at the source, matching the existing api/config.py convention. Sites hardened: api/onboarding.py:578 (_provider_api_key_present) api/providers.py:1051 (_provider_has_shadowed_codex_oauth_value) api/providers.py:1219 (_provider_has_key) api/providers.py:1265 (_get_provider_api_key) api/providers.py:2348 (get_providers catalog) api/providers.py:2781 (_clean_provider_key_from_config) api/routes.py:5045 (_context_length_config_api_key_for_provider) api/routes.py:5110 (_context_length_lookup_inputs_for_model) api/routes.py:15916 (_handle_live_models) Adds tests/test_none_providers_config_guard.py: per-file source-form pins (fail on master / on any single-file revert) plus behavioural checks driving the real functions with `providers: None` to prove no crash and parity with the empty-mapping config. Updates the existing test_issue3717 routes-string pin to match the hardened form. This is a focused salvage of grab-bag PR #3967 -- only the None-providers hardening is taken; the PR's unrelated changes (account-usage worker-pool refactor, function removals) are intentionally dropped. Co-authored-by: lidi1011 <lidi1011@users.noreply.github.com>
fix: harden None providers config across onboarding/providers/routes (salvage of #3967)
Summary
Eight small fixes across 6 files:
1. Lower CLI session display threshold
CLI_MIN_UNTITLED_USER_MESSAGE_COUNT2→1, so single-message CLI sessions appear in the sidebar.2. Handle None value for
providersconfig keycfg.get("providers", {})with(cfg.get("providers") or {})(17 call sites total).Why: When the config YAML has a
providers:key whose value isnull/empty, Python'sdict.get("providers", {})returnsNoneinstead of{}— the default is only used when the key is absent, not when it holds None. Downstream.get()calls then crash. Theor {}pattern correctly handles both absent and None-valued keys.3. Tighten custom provider auto-detection guards
"custom"provider when auto-detected models exist (and not auto_detected_models)custom/localPIDs only (elif pid in ("custom", "local") and auto_detected_models:), preventing non-custom providers from incorrectly inheriting the global fallback list4. Clear stale cross-profile session on profile switch