{"record":{"id":"4c5b1228c861f0cd","repo":"bytedance/deer-flow","slug":"thread-thread-id-has-no-checkpoint","errorCode":null,"errorMessage":"Thread {thread_id} has no checkpoint","messagePattern":"Thread (.+?) has no checkpoint","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"backend/app/gateway/routers/thread_runs.py","lineNumber":640,"sourceCode":"    if record is None:\n        return None\n    if getattr(record, \"thread_id\", None) != thread_id:\n        return None\n    if _run_status_value(record) != RunStatus.interrupted.value:\n        return None\n    return source_run_id\n\n\nasync def _prepare_regenerate_payload(thread_id: str, message_id: str, request: Request) -> RegeneratePrepareResponse:\n    accessor, latest_config = await build_thread_checkpoint_state_accessor(request, thread_id=thread_id)\n    try:\n        latest_checkpoint = await accessor.aget(latest_config)\n    except Exception as exc:\n        logger.exception(\"Failed to read latest checkpoint for regenerate thread %s\", thread_id)\n        raise HTTPException(status_code=500, detail=\"Failed to read latest checkpoint\") from exc\n    latest_checkpoint_id = _checkpoint_configurable(latest_checkpoint).get(\"checkpoint_id\")\n    if not latest_checkpoint_id:\n        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\")","sourceCodeStart":622,"sourceCodeEnd":658,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/thread_runs.py#L622-L658","documentation":"Raised in _prepare_regenerate_payload (HTTP 404) when the thread's latest checkpoint exists as a tuple but its configurable carries no checkpoint_id. Practically: the thread has no addressable checkpoint state (no completed graph step), so there is nothing to regenerate from.","triggerScenarios":"Regenerate-prepare on a brand-new thread with zero runs; a run that was created but crashed before its first checkpoint write; checkpoint store emptied for the thread.","commonSituations":"UI shows a regenerate button before the first response; user deleted thread history; run interrupted pre-checkpoint.","solutions":["Send at least one message and let a run complete before offering regenerate.","Confirm via GET thread state that a checkpoint_id is present.","If history was wiped intentionally, treat the thread as new and start a fresh run."],"exampleFix":"// before\nregeneratePrepare(threadId, messageId);\n// after\nconst st = await getThreadState(threadId);\nif (!st.checkpoint_id) throw new Error('Nothing to regenerate yet');\nregeneratePrepare(threadId, messageId);","handlingStrategy":"validation","validationCode":"const st = await getThreadState(threadId);\nif (!st?.checkpoint_id) { notify('Send a message first'); return; }","typeGuard":"function threadHasCheckpoint(st: {checkpoint_id?: string | null}): boolean {\n  return typeof st.checkpoint_id === 'string' && st.checkpoint_id.length > 0;\n}","tryCatchPattern":"try { await regeneratePrepare(threadId, messageId); } catch (e) { if (e.status === 404 && /has no checkpoint/.test(e.detail)) { hideRegenerateButton(); } else throw e; }","preventionTips":["Show regenerate only after first completed response","Check thread state before enabling rewind actions"],"tags":["regenerate","checkpoint","http-404","empty-thread"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}