{"record":{"id":"5207cb1cb010a27d","repo":"bytedance/deer-flow","slug":"only-the-latest-assistant-message-can-be-regenerat","errorCode":null,"errorMessage":"Only the latest assistant message can be regenerated","messagePattern":"Only the latest assistant message can be regenerated","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"backend/app/gateway/routers/thread_runs.py","lineNumber":662,"sourceCode":"    if target_index is None:\n        # A response interrupted during an LLM call can be visible in the live\n        # stream without ever reaching a checkpoint. The server-stamped run ID\n        # on the latest user message is the durable link to that partial turn.\n        previous_human = next(\n            (message for message in reversed(messages) if _is_visible_human_message(message)),\n            None,\n        )\n        target_run_id = await _find_interrupted_target_run_id(thread_id, previous_human, request) if previous_human is not None else None\n        if target_run_id is None:\n            raise HTTPException(status_code=404, detail=f\"Message {message_id} not found\")\n    else:\n        target_message = messages[target_index]\n        if not _is_visible_ai_message(target_message):\n            raise HTTPException(status_code=409, detail=\"Only visible assistant messages can be regenerated\")\n\n        latest_visible_ai = next((message for message in reversed(messages) if _is_visible_ai_message(message)), None)\n        if _message_id(latest_visible_ai) != message_id:\n            raise HTTPException(status_code=409, detail=\"Only the latest assistant message can be regenerated\")\n\n        previous_human = next((message for message in reversed(messages[:target_index]) if _is_visible_human_message(message)), None)\n        target_run_id = (\n            await _find_target_run_id(\n                thread_id,\n                message_id,\n                target_message,\n                previous_human,\n                request,\n            )\n            if previous_human is not None\n            else None\n        )\n    if previous_human is None:\n        raise HTTPException(status_code=409, detail=\"Could not find the user message for this assistant response\")\n    if target_run_id is None:\n        raise HTTPException(status_code=409, detail=\"Could not find source run for assistant message\")\n    previous_human_id = _message_id(previous_human)","sourceCodeStart":644,"sourceCodeEnd":680,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/thread_runs.py#L644-L680","documentation":"Raised in _prepare_regenerate_payload (HTTP 409) when the target assistant message is visible but is not the most recent visible AI message in the checkpoint. Regeneration rewinds the thread to replay, so only the latest assistant response qualifies; older ones would silently discard later conversation.","triggerScenarios":"Regenerating an assistant message from an earlier turn while newer assistant responses exist in checkpoint state; UI keeps an older message selected after the thread advanced.","commonSituations":"Stale transcript view; concurrent client advanced the thread; user scrolls up and regenerates an old answer.","solutions":["Refresh thread state and regenerate the last visible assistant message only.","Disable regenerate buttons on all but the newest assistant turn in the UI.","If an older answer must change, edit the latest turn or start a new thread with clarifying context."],"exampleFix":"// before\nregeneratePrepare(threadId, anyAiMessageId);\n// after\nconst lastAi = [...messages].reverse().find(m => m.type === 'ai' && !m.hidden);\nif (lastAi?.id === chosenId) regeneratePrepare(threadId, chosenId);","handlingStrategy":"validation","validationCode":"const lastAi = [...messages].reverse().find(m => m.type === 'ai' && !m.hidden);\nif (lastAi?.id !== messageId) { targetLatestInstead(); }","typeGuard":"function isLatestVisibleAi(messages: Msg[], id: string): boolean {\n  for (let i = messages.length - 1; i >= 0; i--) {\n    if (messages[i].type === 'ai' && !messages[i].hidden) return messages[i].id === id;\n  }\n  return false;\n}","tryCatchPattern":"try { await regeneratePrepare(threadId, messageId); } catch (e) { if (e.status === 409 && /latest assistant message/.test(e.detail)) { await refreshThread(threadId); } else throw e; }","preventionTips":["Only the newest assistant turn gets a regenerate control","Refresh state after any run completes before showing rewind UI"],"tags":["regenerate","latest-message","http-409"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}