bytedance/deer-flow · error · HTTPException
Feedback {feedback_id} not found in run {run_id}
Error message
Feedback {feedback_id} not found in run {run_id} What it means
Raised by DELETE /threads/{thread_id}/runs/{run_id}/feedback/{feedback_id} with status 404 when the feedback record exists but its thread_id or run_id does not match the URL path values. Like error 223 it uses 404 rather than 403 to avoid leaking which feedback ids exist under other threads/runs.
Source
Thrown at backend/app/gateway/routers/feedback.py:185
return await feedback_repo.aggregate_by_run(thread_id, run_id)
@router.delete("/{thread_id}/runs/{run_id}/feedback/{feedback_id}")
@require_permission("threads", "delete", owner_check=True, require_existing=True)
async def delete_feedback(
thread_id: ThreadId,
run_id: str,
feedback_id: str,
request: Request,
) -> dict[str, bool]:
"""Delete a feedback record."""
feedback_repo = get_feedback_repo(request)
# Verify feedback belongs to the specified thread/run before deleting
existing = await feedback_repo.get(feedback_id)
if existing is None:
raise HTTPException(status_code=404, detail=f"Feedback {feedback_id} not found")
if existing.get("thread_id") != thread_id or existing.get("run_id") != run_id:
raise HTTPException(status_code=404, detail=f"Feedback {feedback_id} not found in run {run_id}")
deleted = await feedback_repo.delete(feedback_id)
if not deleted:
raise HTTPException(status_code=404, detail=f"Feedback {feedback_id} not found")
return {"success": True}
View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Build the delete URL from a single feedback object that carries its own thread_id, run_id, and id.
- On 404, refresh the feedback list for the target run and reconcile ids before retrying.
- Add client-side assertions in tests that URL path ids match the record being deleted.
Example fix
// before
await del(`/api/threads/${tid}/runs/${rid}/feedback/${fid}`); // ids from different sources
// after
const target = feedbackList.find(f => f.id === fid);
if (!target || target.thread_id !== tid || target.run_id !== rid) throw new Error('feedback does not belong to this run');
await del(`/api/threads/${target.thread_id}/runs/${target.run_id}/feedback/${target.id}`); Defensive patterns
Strategy: validation
Validate before calling
const ok = fb.thread_id === threadId && fb.run_id === runId;
if (!ok) throw new Error('feedback does not belong to this thread/run');
await del(`/api/threads/${fb.thread_id}/runs/${fb.run_id}/feedback/${fb.id}`); Type guard
const feedbackInRun = (f: Feedback, tid: string, rid: string) => f.thread_id === tid && f.run_id === rid;
Prevention
- Build the delete URL from one feedback object
- Refresh the list on 404 and reconcile
- Assert id consistency in tests
When it happens
Trigger: Deleting /threads/A/runs/R1/feedback/F where F belongs to thread B or run R2; URL assembled from mismatched ids (thread from one route param, feedback from another view's state); concurrent re-parenting of records during testing.
Common situations: See trigger scenarios.
Related errors
- No feedback found for this run
- Feedback {feedback_id} not found
- Thread {thread_id} not found
- Agent '{name}' not found
- Artifact not found: {path}
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/f7fe9c4e12d072a9.
Report an issue: GitHub.