{"record":{"id":"3a03aa273a111b5d","repo":"HKUDS/DeepTutor","slug":"a-run-is-already-in-progress-for-layer-key","errorCode":null,"errorMessage":"a run is already in progress for {layer}/{key}","messagePattern":"a run is already in progress for (.+?)/(.+?)","errorType":"error_code","errorClass":"RunBusyError","httpStatus":null,"severity":"warning","filePath":"deeptutor/services/memory/consolidator/runs.py","lineNumber":175,"sourceCode":"        self,\n        *,\n        layer: str,\n        key: str,\n        mode: RunMode,\n        runner: Callable[[Callable[[dict[str, Any]], Awaitable[None]]], Awaitable[None]],\n        params: dict[str, Any] | None = None,\n        language: str = \"en\",\n        user_label: str = \"anonymous\",\n    ) -> Run:\n        \"\"\"Register and kick off a new run.\n\n        ``runner`` is an awaitable factory: takes a ``on_event`` callback\n        and runs the consolidator mode. The manager wires the callback to\n        the event buffer + waiter machinery.\n        \"\"\"\n        async with self._lock:\n            if self.active_for(layer, key) is not None:\n                raise RunBusyError(f\"a run is already in progress for {layer}/{key}\")\n            run = Run(\n                id=uuid.uuid4().hex,\n                layer=layer,\n                key=key,\n                mode=mode,\n                params=dict(params or {}),\n                language=language,\n                user_label=user_label,\n                status=\"queued\",\n                started_at=_now_iso(),\n            )\n            self._runs[run.id] = run\n            self._order.append(run.id)\n            self._active[(layer, key)] = run.id\n            self._evict_if_needed()\n\n        run._task = asyncio.create_task(self._drive(run, runner))\n        return run","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/memory/consolidator/runs.py#L157-L193","documentation":"RunManager.start raises RunBusyError when a consolidation run is already active for the same layer/key pair. The manager serializes runs per (layer, key) under an async lock so two consolidators never write the same memory document concurrently; starting a second run for the same target while the first is still executing is rejected.","triggerScenarios":"Calling runs.start(layer, key, ...) twice without awaiting/finishing the first run — e.g. a scheduler firing consolidation again while a previous run is still in progress, or refs_in_span_l3 triggering a nested/overlapping run for the same key.","commonSituations":"Cron/background consolidation overlapping with a user-triggered consolidation; concurrent WebSocket/API sessions consolidating the same KB; retry logic that starts a new run instead of polling the active one.","solutions":["Check manager.active_for(layer, key) before starting; if a run exists, subscribe to its events or poll its status instead of starting a new one","Catch RunBusyError and wait for the active run to finish (await its completion event) then decide whether a re-run is still needed","Debounce/schedule consolidation so overlapping triggers for the same layer/key collapse into one run","Cancel the active run first via the cancel API if the new run must supersede it"],"exampleFix":"// before\nrun = await manager.start(layer, key, mode, runner)\n\n// after\nif manager.active_for(layer, key) is not None:\n    await manager.wait_for_completion(layer, key)\nrun = await manager.start(layer, key, mode, runner)","handlingStrategy":"fallback","validationCode":"if manager.active_for(layer, key) is not None:\n    existing = manager.active_for(layer, key)\n    # subscribe/poll existing instead of starting\n    return await manager.wait_for_completion(layer, key)","typeGuard":null,"tryCatchPattern":"try:\n    run = await manager.start(layer, key, mode, runner)\nexcept RunBusyError:\n    await manager.wait_for_completion(layer, key)  # piggyback on active run\n    run = None  # decide whether a fresh run is still needed","preventionTips":["Always check active_for(layer, key) before start","Debounce scheduled consolidation per (layer, key)","Expose run status in UIs so users see a run is already going"],"tags":["concurrency","lock","memory","consolidation","busy"],"backgroundTag":"resource-already-in-use","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}