{"record":{"id":"30eeec1b5247f957","repo":"datawhalechina/hello-agents","slug":"user-id-run","errorCode":null,"errorMessage":"user_id 与 run 不匹配","messagePattern":"user_id 与 run 不匹配","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"Co-creation-projects/Shawnxyxy-HealthRecordAgent/backend/api/routes/diet.py","lineNumber":164,"sourceCode":"        raise HTTPException(status_code=404, detail=\"未找到该饮食推荐 run\")\n    return build_diet_observability(row)\n\n\n@router.post(\"/diet/runs/{run_id}/replay\")\nasync def diet_run_replay(\n    run_id: str,\n    body: DietReplayRequest | None = Body(default=None),\n):\n    \"\"\"\n    阶段 3：用该 run 落库的 input 重跑流水线（新 run_id；列 replayed_from_run_id 与 output.replayed_from 溯源）。\n    Mock 工具确定性较高，LLM 输出仍可能不同。\n    \"\"\"\n    rid = run_id.strip()\n    row = get_diet_run(rid)\n    if not row:\n        raise HTTPException(status_code=404, detail=\"run 不存在\")\n    if body and body.user_id and body.user_id.strip() != row[\"user_id\"]:\n        raise HTTPException(status_code=403, detail=\"user_id 与 run 不匹配\")\n    try:\n        return await replay_diet_run(rid)\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail=str(e))\n","sourceCodeStart":146,"sourceCodeEnd":169,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/Shawnxyxy-HealthRecordAgent/backend/api/routes/diet.py#L146-L169","documentation":"HTTP 403 raised by POST /diet/runs/{run_id}/replay when an optional user_id is supplied in the body and, after stripping, does not equal the run's stored user_id. It prevents replaying another user's run under your identity; omitting user_id in the body skips the check entirely.","triggerScenarios":"POST /diet/runs/{rid}/replay with body {\"user_id\": \"bob\"} for a run created by 'alice' — including whitespace/case mismatches.","commonSituations":"Client auto-injecting the current logged-in user into every request body; switching test users between recommend and replay.","solutions":["Send the same user_id that owns the run, or omit user_id from the replay body.","Retrieve the owning user via GET /diet/runs/{run_id} if unsure.","Normalize (trim) user_id client-side to avoid false mismatches."],"exampleFix":"# before\nrequests.post(f'{base}/diet/runs/{rid}/replay', json={'user_id': 'bob'})  # 403\n\n# after\nrequests.post(f'{base}/diet/runs/{rid}/replay', json={'user_id': 'alice'})\n# or simply:\nrequests.post(f'{base}/diet/runs/{rid}/replay')","handlingStrategy":"validation","validationCode":"row = requests.get(f\"{base}/diet/runs/{rid}\").json()\nbody = {\"user_id\": row[\"user_id\"]} if include_owner else None\nrequests.post(f\"{base}/diet/runs/{rid}/replay\", json=body)","typeGuard":null,"tryCatchPattern":"try:\n    requests.post(f\"{base}/diet/runs/{rid}/replay\", json=body)\nexcept HTTPError as e:\n    if e.response.status_code == 403:\n        body.pop(\"user_id\", None)  # omit optional owner check and retry once\n        requests.post(f\"{base}/diet/runs/{rid}/replay\")\n    else:\n        raise","preventionTips":["Either omit user_id in replay bodies or source it from the run row itself.","Never auto-inject the session user into optional ownership fields.","Normalize whitespace before comparing ids client-side."],"tags":["fastapi","http-403","authorization","replay"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}