bytedance/deer-flow · warning · HTTPException
Thread not found
Error message
Thread not found
What it means
Raised as HTTP 404 by POST /api/scheduled-tasks in reuse_thread mode when thread_store.check_access(thread_id, user_id, require_existing=True) fails — the thread does not exist, or exists but is not accessible/owned by the requesting user. Deliberately 404 (not 403) to avoid leaking thread existence.
Source
Thrown at backend/app/gateway/routers/scheduled_tasks.py:83
return await repo.list_by_user(str(user.id))
@router.post("/scheduled-tasks")
@require_permission("threads", "write")
async def create_scheduled_task(request: Request, body: ScheduledTaskCreateRequest):
config = get_config()
repo = get_scheduled_task_repo(request)
thread_store = get_thread_store(request)
user = await get_optional_user_from_request(request)
if user is None:
raise HTTPException(status_code=401, detail="Authentication required")
if body.context_mode not in {"fresh_thread_per_run", "reuse_thread"}:
raise HTTPException(status_code=422, detail="Unsupported context_mode")
if body.context_mode == "reuse_thread":
if not body.thread_id:
raise HTTPException(status_code=422, detail="reuse_thread requires thread_id")
if not await thread_store.check_access(body.thread_id, str(user.id), require_existing=True):
raise HTTPException(status_code=404, detail="Thread not found")
if body.schedule_type not in {"once", "cron"}:
raise HTTPException(status_code=422, detail="Unsupported schedule_type")
schedule_spec = dict(body.schedule_spec)
try:
validate_timezone(body.timezone)
if body.schedule_type == "cron":
raw_cron = schedule_spec.get("cron")
if not isinstance(raw_cron, str):
raise HTTPException(status_code=422, detail="cron schedule requires schedule_spec.cron")
schedule_spec["cron"] = normalize_cron_expression(raw_cron)
next_run_at = compute_next_run_at(
body.schedule_type,
schedule_spec,
body.timezone,
now=datetime.now(UTC),
)
except ValueError as exc:View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Verify the thread exists and is owned by (or shared with) the current user via the threads endpoint.
- Use a fresh, valid thread id, or switch context_mode to fresh_thread_per_run.
- Re-check ownership if the thread belongs to a different user — access is enforced here.
Defensive patterns
Strategy: validation
Validate before calling
threads = requests.get(f"{BASE}/api/threads", headers=auth).json()
owned = {t["id"] for t in threads.get("threads", [])}
assert body["thread_id"] in owned, "thread missing or not accessible for this user" Type guard
const isAccessibleThread = (tid: string, ownedIds: Set<string>): boolean => ownedIds.has(tid);
Try / catch
resp = requests.post(f"{BASE}/api/scheduled-tasks", json=body, headers=auth)
if resp.status_code == 404 and body["context_mode"] == "reuse_thread":
body["thread_id"] = await pick_existing_thread() # recover
resp = requests.post(f"{BASE}/api/scheduled-tasks", json=body, headers=auth) Prevention
- Offer only currently accessible threads in the task-creation picker.
- Remember 404 here can mean 'not yours', not just 'missing' — don't leak existence assumptions to users.
- Refresh the thread list when the picker opens.
When it happens
Trigger: Creating a reuse_thread task with a deleted thread id, another user's thread id, or a malformed id.
Common situations: Thread was deleted before the task was created; switching accounts; stale thread id cached in the UI.
Related errors
- Thread {thread_id} not found
- Scheduled task not found
- Run {run_id} not found
- reuse_thread requires thread_id
- Thread {thread_id} not found
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/7ace105114da8cea.
Report an issue: GitHub.