{"record":{"id":"f13beef9b5ed3c6d","repo":"bytedance/deer-flow","slug":"thread-has-a-run-in-flight-save-after-the-run-fin","errorCode":null,"errorMessage":"Thread has a run in flight. Save after the run finishes.","messagePattern":"Thread has a run in flight\\. Save after the run finishes\\.","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"error","filePath":"backend/app/gateway/routers/artifacts.py","lineNumber":484,"sourceCode":"            if not bool(getattr(sandbox_provider, \"uses_thread_data_mounts\", False)):\n                sandbox_id = await sandbox_provider.acquire_async(thread_id, user_id=effective_user_id)\n                sandbox = sandbox_provider.get(sandbox_id)\n                if sandbox is None:\n                    raise RuntimeError(\"Failed to acquire sandbox for artifact update\")\n\n            try:\n                if sandbox is not None:\n                    await asyncio.to_thread(_sync_artifact_to_sandbox, sandbox, virtual_path, updated)\n                await asyncio.to_thread(_replace_artifact_atomically, actual_path, updated, file_stat)\n            except Exception:\n                if sandbox is not None:\n                    try:\n                        await asyncio.to_thread(_sync_artifact_to_sandbox, sandbox, virtual_path, current)\n                    except Exception:\n                        logger.exception(\"Failed to roll back remote artifact after artifact update failure: %s\", virtual_path)\n                raise\n    except ConflictError:\n        raise HTTPException(status_code=409, detail=\"Thread has a run in flight. Save after the run finishes.\") from None\n    except HTTPException:\n        raise\n    except Exception:\n        logger.exception(\"Failed to update artifact %s for thread %s\", path, thread_id)\n        raise HTTPException(status_code=500, detail=\"Failed to update artifact\") from None\n    finally:\n        if sandbox_id is not None and sandbox_provider is not None:\n            try:\n                await asyncio.to_thread(sandbox_provider.release, sandbox_id)\n            except Exception:\n                logger.warning(\"Failed to release sandbox after artifact update: %s\", sandbox_id, exc_info=True)\n\n    content_sha256 = hashlib.sha256(updated).hexdigest()\n    return ArtifactUpdateResponse(\n        path=virtual_path,\n        sha256=content_sha256,\n        size=len(updated),\n    )","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/artifacts.py#L466-L502","documentation":"The artifacts PUT/update route translates a harness ConflictError into HTTP 409 with this message. The artifact store refuses writes while the owning thread has an active run, because the run may mutate or read the same file and an overwrite would race it.","triggerScenarios":"PUT or PATCH on /api/threads/{thread_id}/artifacts/{path} while a run/stream for that thread is in flight (agent still executing, or a previous run was not fully finalized). Editing a file in the UI while generation is streaming is the canonical case.","commonSituations":"User edits an artifact in the frontend editor while the agent is still generating; a stale frontend believes the run ended but the backend has not released the run lock; long-running tool execution keeps the run open for minutes.","solutions":["Wait for the run to finish (run status endpoint / stream 'end' event) and retry the save","If the UI knows a run is active, disable the save button or buffer the edit and auto-save on run completion","If no run should be active, check for orphaned run state in the thread and finalize it (restart Gateway if the run registry is stale in-memory state)","Do not retry in a tight loop: poll run status and save once it reports idle"],"exampleFix":"// before\nawait saveArtifact(threadId, path, content); // fires during streaming\n\n// after\nif (isRunActive(threadId)) {\n  queueEdit(threadId, path, content); // replayed on run end event\n} else {\n  await saveArtifact(threadId, path, content);\n}","handlingStrategy":"retry","validationCode":"const run = await getActiveRun(threadId);\nif (run && run.status !== 'ended') {\n  throw new Error('Thread has a run in flight; defer save');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await saveArtifact(threadId, path, content);\n} catch (e) {\n  if (e.status === 409) { await waitForRunEnd(threadId); await saveArtifact(threadId, path, content); return; }\n  throw e;\n}","preventionTips":["Subscribe to the thread's run/stream events and gate the editor save action on run-idle state","Buffer edits made during a run and flush them exactly once on the run-end event"],"tags":["artifacts","http-409","concurrency","threads"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}