{"record":{"id":"f7fe9c4e12d072a9","repo":"bytedance/deer-flow","slug":"feedback-feedback-id-not-found-in-run-run-id","errorCode":null,"errorMessage":"Feedback {feedback_id} not found in run {run_id}","messagePattern":"Feedback (.+?) not found in run (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"backend/app/gateway/routers/feedback.py","lineNumber":185,"sourceCode":"    return await feedback_repo.aggregate_by_run(thread_id, run_id)\n\n\n@router.delete(\"/{thread_id}/runs/{run_id}/feedback/{feedback_id}\")\n@require_permission(\"threads\", \"delete\", owner_check=True, require_existing=True)\nasync def delete_feedback(\n    thread_id: ThreadId,\n    run_id: str,\n    feedback_id: str,\n    request: Request,\n) -> dict[str, bool]:\n    \"\"\"Delete a feedback record.\"\"\"\n    feedback_repo = get_feedback_repo(request)\n    # Verify feedback belongs to the specified thread/run before deleting\n    existing = await feedback_repo.get(feedback_id)\n    if existing is None:\n        raise HTTPException(status_code=404, detail=f\"Feedback {feedback_id} not found\")\n    if existing.get(\"thread_id\") != thread_id or existing.get(\"run_id\") != run_id:\n        raise HTTPException(status_code=404, detail=f\"Feedback {feedback_id} not found in run {run_id}\")\n    deleted = await feedback_repo.delete(feedback_id)\n    if not deleted:\n        raise HTTPException(status_code=404, detail=f\"Feedback {feedback_id} not found\")\n    return {\"success\": True}\n","sourceCodeStart":167,"sourceCodeEnd":190,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/feedback.py#L167-L190","documentation":"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.","triggerScenarios":"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.","commonSituations":"See trigger scenarios.","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."],"exampleFix":"// before\nawait del(`/api/threads/${tid}/runs/${rid}/feedback/${fid}`); // ids from different sources\n\n// after\nconst target = feedbackList.find(f => f.id === fid);\nif (!target || target.thread_id !== tid || target.run_id !== rid) throw new Error('feedback does not belong to this run');\nawait del(`/api/threads/${target.thread_id}/runs/${target.run_id}/feedback/${target.id}`);","handlingStrategy":"validation","validationCode":"const ok = fb.thread_id === threadId && fb.run_id === runId;\nif (!ok) throw new Error('feedback does not belong to this thread/run');\nawait del(`/api/threads/${fb.thread_id}/runs/${fb.run_id}/feedback/${fb.id}`);","typeGuard":"const feedbackInRun = (f: Feedback, tid: string, rid: string) =>\n  f.thread_id === tid && f.run_id === rid;","tryCatchPattern":null,"preventionTips":["Build the delete URL from one feedback object","Refresh the list on 404 and reconcile","Assert id consistency in tests"],"tags":["http-404","feedback","resource-mismatch","rest-api"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}