From 5c0236ac3ee3ecc1d13dbf656bc74913c3f63bdc Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Wed, 21 Oct 2026 17:20:36 +0000 Subject: [PATCH] fix(cloud): keep the directory projection fresh during startup Boot continues for minutes after the first directory snapshot is committed: platform ports reconnect, knowledge bases and pipelines reload, and a cold shared Runtime restore may still be running. The refresh loop only started at the end of that stage, so any boot slower than the projection staleness window failed closed while restoring knowledge bases, and Core then exited during startup - every retry hit the same window. Start the single refresh task as soon as the first snapshot is committed and keep the later start as a guarded fallback so the loop is never created twice. Observed on the production Cloud instance: a boot that took ~120s to reach knowledge base restore raised 'Cloud directory projection is stale' and the process exited, while the same image had booted earlier with a faster start. Co-authored-by: multica-agent --- src/langbot/pkg/core/stages/build_app.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/langbot/pkg/core/stages/build_app.py b/src/langbot/pkg/core/stages/build_app.py index dd2e060d5..c42dd769e 100644 --- a/src/langbot/pkg/core/stages/build_app.py +++ b/src/langbot/pkg/core/stages/build_app.py @@ -173,6 +173,17 @@ class BuildAppStage(stage.BootingStage): ) await directory_projection_service.initialize() ap.directory_projection_service = directory_projection_service + # Startup continues for a while after the first snapshot: platform + # ports reconnect, knowledge bases and pipelines reload, and a cold + # shared Runtime restore may still be running. Workspace admission + # fails closed once the projection is older than its staleness + # window, so keep refreshing it from the moment the first snapshot + # is committed instead of after the rest of this stage. + ap.directory_projection_task = ap.task_mgr.create_task( + directory_projection_service.run(), + name='cloud-directory-projection', + scopes=[core_entities.LifecycleControlScope.APPLICATION], + ) workspace_policy = deployment.workspace_policy workspace_service_inst = workspace_service_module.WorkspaceService( @@ -313,10 +324,10 @@ class BuildAppStage(stage.BootingStage): async def runtime_disconnect_callback(connector: plugin_connector.PluginRuntimeConnector) -> None: connector.schedule_reconnect() - if ap.directory_projection_service is not None: - # Keep the projection fresh while shared Runtime cold restore runs. - # BuildApp initializes the connector before Application.run() starts - # its long-lived tasks, so start the single refresh task here. + if ap.directory_projection_service is not None and getattr(ap, 'directory_projection_task', None) is None: + # Fallback for a deployment that reached this point without the + # startup task (see the projection start right after the first + # snapshot is committed). ap.directory_projection_task = ap.task_mgr.create_task( ap.directory_projection_service.run(), name='cloud-directory-projection',