{"record":{"id":"4851b4b6e6320ce7","repo":"bytedance/deer-flow","slug":"str-e-4851b4","errorCode":null,"errorMessage":"str(e)","messagePattern":"str\\(e\\)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/app/gateway/routers/uploads.py","lineNumber":320,"sourceCode":"async def upload_files(\n    thread_id: ThreadId,\n    request: Request,\n    files: list[UploadFile] = File(...),\n    config: AppConfig = Depends(get_config),\n) -> UploadResponse:\n    \"\"\"Upload multiple files to a thread's uploads directory.\"\"\"\n    if not files:\n        raise HTTPException(status_code=400, detail=\"No files provided\")\n\n    limits = _get_upload_limits(config)\n    if len(files) > limits.max_files:\n        raise HTTPException(status_code=413, detail=f\"Too many files: maximum is {limits.max_files}\")\n\n    try:\n        effective_user_id = get_effective_user_id()\n        uploads_dir = await run_file_io(ensure_uploads_dir, thread_id, user_id=effective_user_id)\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail=str(e))\n    sandbox_uploads = uploads_dir\n    uploaded_files = []\n    written_paths = []\n    sandbox_sync_targets = []\n    skipped_files = []\n    total_size = 0\n    # Track filenames within this request so duplicate form parts do not\n    # silently truncate each other. Existing uploads keep the historical\n    # overwrite behavior for a single replacement upload.\n    seen_filenames: set[str] = set()\n\n    sandbox_provider = get_sandbox_provider()\n    sync_to_sandbox = not _uses_thread_data_mounts(sandbox_provider)\n    sandbox = None\n    if sync_to_sandbox:\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:","sourceCodeStart":302,"sourceCodeEnd":338,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/uploads.py#L302-L338","documentation":"HTTP 400 raised when ensure_uploads_dir fails with a ValueError while preparing the thread's uploads directory (e.g. the thread_id does not resolve to a valid directory path). The raw exception text is surfaced as the detail. It fires before any file is written, so the request is rejected atomically.","triggerScenarios":"Uploading to a thread whose id is malformed or cannot be mapped to a storage path (ensure_uploads_dir raises ValueError); uploading to a thread that was deleted or whose per-user directory cannot be created/resolved.","commonSituations":"Client generates a thread id locally instead of using one returned by the threads API; a thread was deleted while an upload was in flight; user-context (effective_user_id) resolves to a path segment the storage layer rejects.","solutions":["Verify the thread exists via the threads API before uploading and use the exact id it returned.","Inspect the returned detail message — it is the ValueError text from ensure_uploads_dir and names the concrete problem (invalid id, unresolvable path).","Re-create the thread and retry the upload if the thread was deleted."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const thread = await fetch(`/api/threads/${encodeURIComponent(tid)}`).then(r => r.ok ? r.json() : null);\nif (!thread) throw new Error('Thread does not exist');","typeGuard":null,"tryCatchPattern":"catch 400 on upload; surface detail text (it names the invalid id/path) and stop — do not blind-retry.","preventionTips":["Always create threads via the API and reuse the returned id.","Drop local thread state when the UI sees a 404/400 on the thread itself."],"tags":["upload","http-400","thread-id","gateway"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}