2525from __future__ import annotations
2626
2727import asyncio
28- from typing import Any , Optional
28+ from typing import Any
2929
3030from sqlalchemy .ext .asyncio import async_sessionmaker
3131
@@ -137,6 +137,19 @@ async def reload_config(self) -> OpenRAGConfig:
137137 self ._cm ._config = None
138138 return await self .load_config ()
139139
140+ async def hydrate_on_startup (self ) -> None :
141+ """Eagerly populate ``config_manager._config`` from the DB at
142+ lifespan startup.
143+
144+ Without this, in `db` mode a restart leaves ``_config = None``
145+ and the synchronous ``get_openrag_config()`` falls back to
146+ defaults (no yaml exists) — so ``/api/settings`` reports
147+ ``onboarding.current_step=0`` and the frontend flashes the
148+ wizard on every restart. ``load_config()`` is itself mode-aware,
149+ so this is safe to call unconditionally.
150+ """
151+ await self .load_config ()
152+
140153 async def is_onboarded (self ) -> bool :
141154 mode = get_storage_mode ()
142155 if mode == "files" :
@@ -155,7 +168,7 @@ async def is_onboarded(self) -> bool:
155168 return False # no yaml fallback in pure-db mode
156169 return self ._cm .load_config ().edited
157170
158- async def get_onboarding_step (self ) -> Optional [ Any ] :
171+ async def get_onboarding_step (self ) -> Any | None :
159172 """Returns the legacy step indicator — usually an int index from
160173 the OnboardingState dataclass, sometimes None. Treat as opaque."""
161174 mode = get_storage_mode ()
@@ -181,10 +194,10 @@ async def get_onboarding_step(self) -> Optional[Any]:
181194
182195 async def save_config (
183196 self ,
184- config : Optional [ OpenRAGConfig ] = None ,
197+ config : OpenRAGConfig | None = None ,
185198 * ,
186199 preserve_edited : bool = False ,
187- actor_user_id : Optional [ str ] = None ,
200+ actor_user_id : str | None = None ,
188201 ) -> bool :
189202 mode = get_storage_mode ()
190203
@@ -217,7 +230,7 @@ async def save_config(
217230
218231 async def update_onboarding_state (
219232 self ,
220- actor_user_id : Optional [ str ] = None ,
233+ actor_user_id : str | None = None ,
221234 ** kwargs : Any ,
222235 ) -> bool :
223236 mode = get_storage_mode ()
@@ -278,8 +291,8 @@ def _install_yaml_write_hooks(self) -> None:
278291 cm ._db_mirror_original_save = cm .save_config_file # type: ignore[attr-defined]
279292 cm ._db_mirror_original_update_ob = cm .update_onboarding_state # type: ignore[attr-defined]
280293
281- original_save = cm ._db_mirror_original_save
282- original_update_ob = cm ._db_mirror_original_update_ob
294+ original_save = cm ._db_mirror_original_save # type: ignore[attr-defined]
295+ original_update_ob = cm ._db_mirror_original_update_ob # type: ignore[attr-defined]
283296
284297 def patched_save (config = None , preserve_edited : bool = False ) -> bool :
285298 mode = get_storage_mode ()
@@ -321,7 +334,7 @@ def patched_update_ob(**kwargs) -> bool:
321334
322335 def _apply_in_memory (
323336 self ,
324- config : Optional [ OpenRAGConfig ] ,
337+ config : OpenRAGConfig | None ,
325338 preserve_edited : bool ,
326339 ) -> None :
327340 """Mirror the in-process effects of ``ConfigManager.save_config_file``
@@ -373,7 +386,7 @@ async def _mirror_to_db(
373386 self ,
374387 config : OpenRAGConfig ,
375388 * ,
376- actor_user_id : Optional [ str ] = None ,
389+ actor_user_id : str | None = None ,
377390 ) -> None :
378391 config_dict = config .to_dict ()
379392
0 commit comments