{"record":{"id":"07e6eeee5cdf3982","repo":"bytedance/deer-flow","slug":"failed-to-delete-local-thread-data-07e6ee","errorCode":null,"errorMessage":"Failed to delete local thread data.","messagePattern":"Failed to delete local thread data\\.","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"backend/app/gateway/routers/threads.py","lineNumber":541,"sourceCode":"# ---------------------------------------------------------------------------\n# Helpers\n# ---------------------------------------------------------------------------\n\n\ndef _delete_thread_data(thread_id: str, paths: Paths | None = None, *, user_id: str | None = None) -> ThreadDeleteResponse:\n    \"\"\"Delete local persisted filesystem data for a thread.\"\"\"\n    path_manager = paths or get_paths()\n    try:\n        path_manager.delete_thread_dir(thread_id, user_id=user_id)\n    except ValueError as exc:\n        raise HTTPException(status_code=422, detail=str(exc)) from exc\n    except FileNotFoundError:\n        # Not critical — thread data may not exist on disk\n        logger.debug(\"No local thread data to delete for %s\", sanitize_log_param(thread_id))\n        return ThreadDeleteResponse(success=True, message=f\"No local data for {thread_id}\")\n    except Exception as exc:\n        logger.exception(\"Failed to delete thread data for %s\", sanitize_log_param(thread_id))\n        raise HTTPException(status_code=500, detail=\"Failed to delete local thread data.\") from exc\n\n    logger.info(\"Deleted local thread data for %s\", sanitize_log_param(thread_id))\n    return ThreadDeleteResponse(success=True, message=f\"Deleted local thread data for {thread_id}\")\n\n\nasync def _fetch_raw_pending_writes(checkpointer: Any, config: dict[str, Any]) -> list[Any]:\n    \"\"\"Fetch pending writes attached to a specific checkpoint.\n\n    Snapshot ``tasks`` only reflect writes that were pending while a task was\n    still scheduled; writes attached to the latest checkpoint afterwards\n    (rollback reattachment, worker error fallback) never surface there, so the\n    status derivation needs one raw tuple fetch on the resolved checkpoint.\n    \"\"\"\n    raw_tuple = await checkpointer.aget_tuple(config)\n    if raw_tuple is None:\n        return []\n    return list(getattr(raw_tuple, \"pending_writes\", ()) or ())\n","sourceCodeStart":523,"sourceCodeEnd":559,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/threads.py#L523-L559","documentation":"Raised by DELETE /threads/{thread_id} when deleting the thread's on-disk data directory raises an unexpected exception (anything other than ValueError, which maps to 422, or FileNotFoundError, which is treated as success). The endpoint delegates to path_manager.delete_thread_dir(thread_id, user_id=user_id); an OSError from the filesystem surfaces here as a 500. The full traceback is logged via logger.exception before the HTTPException is raised.","triggerScenarios":"DELETE /api/threads/{thread_id} while the data directory or files inside it are unreadable/unwritable (PermissionError, EACCES), a path component was replaced by a file, the disk is full or read-only, or an external process holds/locks files during deletion on Windows-style mounts.","commonSituations":"Data dir created by root in a container then served by a non-root user; volume permission drift after chown/chmod; NFS/FUSE mounts returning EIO; concurrent deletion races where a rmtree partially fails after FileNotFoundError of an inner entry was handled but other inner errors are not.","solutions":["Check the Gateway logs for the logger.exception('Failed to delete thread data for %s') traceback — it names the exact OSError and path class involved.","Verify the Gateway process user owns or can write the configured thread data root (check config.yaml persistence paths) — fix ownership with chown -R on the data dir.","Retry the DELETE once transient filesystem conditions (lock, mount flap) clear; confirm no other process (backup job, another Gateway replica) is traversing the dir.","If it recurs on a specific thread, inspect that thread's directory manually and remove it by hand, then re-issue the DELETE (FileNotFoundError path then returns success)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await fetch(`/api/threads/${id}`, {method: 'DELETE'});\n} catch (e) {\n  // network-level failure only; HTTP 500 arrives as a normal response\n} \n// on 500: surface 'deletion failed, retry' and keep the thread in the list; on 404/200 remove it","preventionTips":["Keep the Gateway process as the owner of its data directory; avoid chown-ing it to other users.","Don't run backup/antivirus jobs that traverse thread data dirs while the Gateway serves deletes.","Treat DELETE as retryable: re-issue on 500, since FileNotFoundError is already handled server-side as success."],"tags":["threads","filesystem","permissions","http-500","delete"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}