{"record":{"id":"d4eb67b3ba39d4a5","repo":"bytedance/deer-flow","slug":"only-visible-assistant-messages-can-be-regenerated","errorCode":null,"errorMessage":"Only visible assistant messages can be regenerated","messagePattern":"Only visible assistant messages can be regenerated","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"backend/app/gateway/routers/thread_runs.py","lineNumber":658,"sourceCode":"        raise HTTPException(status_code=404, detail=f\"Thread {thread_id} has no checkpoint\")\n\n    messages = _checkpoint_messages(latest_checkpoint)\n    target_index = next((i for i, message in enumerate(messages) if _message_id(message) == message_id), None)\n    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:","sourceCodeStart":640,"sourceCodeEnd":676,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/thread_runs.py#L640-L676","documentation":"Raised in _prepare_regenerate_payload (HTTP 409) when the found target message (by id) is not a visible AI message. Regeneration only applies to assistant messages that are visible in the conversation; human messages, tool messages, or AI messages marked hidden (e.g. internal reasoning) are rejected.","triggerScenarios":"Passing the id of a user message or tool-call message to the regenerate endpoint; targeting an assistant message with hide_from_ui set.","commonSituations":"Frontend passes the wrong message from the transcript (e.g. a tool result card); internal/system AI messages leaking into UI and being targeted.","solutions":["Only expose regenerate on visible assistant text bubbles in the UI.","Filter the messages array by type === 'ai' and not hidden before computing the target.","Verify the id being sent corresponds to the assistant reply, not the human turn (which uses the edit flow instead)."],"exampleFix":"// before\nregeneratePrepare(threadId, clickedMessageId);\n// after\nconst target = messages.find(m => m.id === clickedMessageId);\nif (target?.type !== 'ai' || target.hidden) return;\nregeneratePrepare(threadId, clickedMessageId);","handlingStrategy":"type-guard","validationCode":"const target = messages.find(m => m.id === messageId);\nif (!target || target.type !== 'ai' || target.hidden) { return; }","typeGuard":"function isRegenerableMessage(m: {type: string; hidden?: boolean} | undefined): boolean {\n  return !!m && m.type === 'ai' && !m.hidden;\n}","tryCatchPattern":"try { await regeneratePrepare(threadId, messageId); } catch (e) { if (e.status === 409 && /visible assistant/.test(e.detail)) { fixMessageTargeting(); } else throw e; }","preventionTips":["Attach regenerate only to visible assistant bubbles","Filter hidden/tool messages out of rewind targets"],"tags":["regenerate","message-visibility","http-409"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}