On a non-default profile, Settings → Providers timed out and the model
picker showed only the default profile's models. WebUI profile switching is
per-client/cookie-scoped (#798), but two read-only paths resolved from the
process-global default profile:
- Facet A: /api/providers + /api/models did not apply the active profile's
.env around the read, so get_auth_status() / provider_model_ids() / custom
key lookups resolved the default profile's credentials. On a non-default
profile the auth probes could stall past the 30s frontend abort.
- Facet B: the /api/models disk cache was a single import-time
STATE_DIR/models_cache.json shared across every profile, while the cache
fingerprint is profile-specific -> a non-default profile rejected the shared
snapshot every read and cold-rebuilt (the slow serial-probe path).
Fix:
- api.profiles.profile_env_for_active_request(): applies the active
per-request profile's .env for the duration of the read (delegates to the
existing profile_env_for_background_worker used by streaming). No-op for the
default/root profile, so single-profile deployments are byte-identical.
- api.config._get_models_cache_path(): profile-keys the disk cache filename
(models_cache.<profile>.json) derived from the default path; default profile
keeps models_cache.json unchanged (no file migration).
- routes.py: wrap both GET handlers in profile_env_for_active_request.
- conftest: restore api.profiles._active_profile + clear request-profile TLS
after each test (a pre-existing isolation hole that profile-keyed cache
paths newly surface under sharding).
Tested: 10 new regression tests; live before/after on isolated servers shows
a non-default profile now surfaces its configured provider (deepseek) with
its own credentials + its own cache file, where master showed only the
default profile's providers and one shared cache.
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>