{"record":{"id":"17cf8a3095acca7d","repo":"bytedance/deer-flow","slug":"feedback-feedback-id-not-found","errorCode":null,"errorMessage":"Feedback {feedback_id} not found","messagePattern":"Feedback (.+?) not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"info","filePath":"backend/app/gateway/routers/feedback.py","lineNumber":183,"sourceCode":"    \"\"\"Get aggregated feedback stats (positive/negative counts) for a run.\"\"\"\n    feedback_repo = get_feedback_repo(request)\n    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":165,"sourceCodeEnd":190,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/feedback.py#L165-L190","documentation":"Raised by DELETE /threads/{thread_id}/runs/{run_id}/feedback/{feedback_id} with status 404 when feedback_repo.get(feedback_id) returns None — the feedback record with that id does not exist. This is the pre-delete existence check; deletion never proceeds on a missing record.","triggerScenarios":"Deleting with a malformed or fabricated feedback_id; deleting feedback already removed via the by-run delete endpoint or by another admin/session; the feedback table/store was reset between listing and deleting.","commonSituations":"Two UI panels offering delete (by-run and by-id) racing each other; stale feedback list in the UI after a concurrent delete; store migrations wiping feedback rows.","solutions":["Re-fetch the feedback list for the run and delete using a fresh feedback_id.","Prefer the simpler by-run delete endpoint (DELETE .../runs/{run_id}/feedback) when deleting the current user's own feedback.","Treat 404 as already-deleted in idempotent clients."],"exampleFix":"// before\nawait del(`/api/threads/${tid}/runs/${rid}/feedback/${staleFeedbackId}`);\n\n// after\nconst fb = (await listFeedback(tid, rid)).find(f => f.id === staleFeedbackId);\nif (fb) await del(`/api/threads/${tid}/runs/${rid}/feedback/${fb.id}`);","handlingStrategy":"validation","validationCode":"const list = await listFeedback(threadId, runId);\nconst fb = list.find(f => f.id === feedbackId);\nif (fb) await del(`/api/threads/${threadId}/runs/${runId}/feedback/${fb.id}`);","typeGuard":null,"tryCatchPattern":"try { await del(url); } catch (e) { if (e.status === 404) return; throw e; }","preventionTips":["Delete using a fresh feedback_id from a just-fetched list","Prefer the by-run delete for the user's own feedback","Treat 404 as already-deleted"],"tags":["http-404","feedback","rest-api","idempotency"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}