{"record":{"id":"6f0149e9651fabd4","repo":"bytedance/deer-flow","slug":"thread-thread-id-not-found","errorCode":null,"errorMessage":"Thread {thread_id} not found","messagePattern":"Thread (.+?) not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"backend/app/gateway/authz.py","lineNumber":524,"sourceCode":"                )\n                if not allowed and getattr(auth.user, \"system_role\", None) == INTERNAL_SYSTEM_ROLE:\n                    # Trusted internal callers (channel workers) also act for\n                    # the connection owner carried in X-DeerFlow-Owner-User-Id.\n                    # Scope the check to that owner instead of bypassing it; a\n                    # leaked internal token must not grant cross-user thread\n                    # access. The header is honored only after ``auth`` proved\n                    # the caller holds the internal token (mirrors\n                    # get_trusted_internal_owner_user_id, which keys off the\n                    # middleware-stamped ``request.state.user``).\n                    header_owner = (request.headers.get(INTERNAL_OWNER_USER_ID_HEADER_NAME) or \"\").strip()\n                    if header_owner:\n                        allowed = await thread_store.check_access(\n                            thread_id,\n                            header_owner,\n                            require_existing=require_existing,\n                        )\n                if not allowed:\n                    raise HTTPException(\n                        status_code=404,\n                        detail=f\"Thread {thread_id} not found\",\n                    )\n\n            return await func(*args, **kwargs)\n\n        return wrapper\n\n    return decorator\n","sourceCodeStart":506,"sourceCodeEnd":534,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/authz.py#L506-L534","documentation":"HTTP 404 raised by require_permission's owner check: either require_existing=true and the thread row does not exist, or the thread exists but is owned by a different user_id, or access via the internal-owner header failed check_access. The 404 (not 403) is deliberate — it does not leak the existence of threads the caller does not own. Note check_access returns True for missing rows (legacy untracked threads) and NULL user_id rows (shared/pre-auth data), so this fires only for an existing row owned by someone else, or a genuinely missing thread when require_existing is set.","triggerScenarios":"Passing another user's thread_id to a thread-scoped endpoint; passing a thread_id that was deleted while require_existing=true; forging the internal-owner header without holding the internal token (the header is only honored after internal-token auth).","commonSituations":"Frontend keeping a stale thread list after switching accounts; sharing thread URLs between users; deleted-thread race in the UI; cross-user API scripting.","solutions":["Use a thread_id that belongs to the authenticated user (re-fetch the thread list)","If shared access is intended, have an owner/admin share or reassign the thread's user_id","Handle the 404 in the client by refreshing or dropping the stale thread from local state"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# Verify the thread is visible to this user before mutating it\nlisting = await client.get(f\"{base}/api/threads\", headers=headers)\nowned = {t[\"thread_id\"] for t in listing.json()}\nif thread_id not in owned:\n    refresh_or_drop_thread(thread_id)  # stale or foreign id — do not call","typeGuard":null,"tryCatchPattern":"try:\n    await client.get(f\"{base}/api/threads/{thread_id}\", headers=headers)\nexcept HTTPStatusError as e:\n    if e.response.status_code == 404:\n        # missing OR owned by someone else — treat identically, refresh local state\n        remove_from_thread_list(thread_id)\n    else:\n        raise","preventionTips":["Always source thread ids from the authenticated user's own thread list response","On account switch or logout, clear cached thread state","Treat 404 as 'not yours or gone' — do not retry with the same credentials"],"tags":["auth","http-404","authorization","ownership","threads","gateway"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}