bytedance/deer-flow · error · HTTPException

Failed to get thread

Error message

Failed to get thread

What it means

500 from GET /threads/{thread_id} when reading the checkpoint snapshot fails: accessor.aget(config) or the follow-up _fetch_raw_pending_writes raises a non-checkpoint-mode exception. Checkpoint-mode errors get their own mapping; everything else (storage failure, deserialization) becomes 'Failed to get thread' with a logged traceback.

Source

Thrown at backend/app/gateway/routers/threads.py:1048

    record: dict | None = await thread_store.get(thread_id)
    try:
        accessor, config = build_checkpoint_state_accessor(
            request,
            thread_id=thread_id,
            assistant_id=record.get("assistant_id") if record is not None else None,
        )
    except _CHECKPOINT_MODE_ERRORS as exc:
        raise _checkpoint_mode_http_error(exc, thread_id) from exc

    try:
        snapshot = await accessor.aget(config)
        checkpoint_id = (snapshot.config or {}).get("configurable", {}).get("checkpoint_id")
        pending_writes = await _fetch_raw_pending_writes(checkpointer, snapshot.config) if checkpoint_id else []
    except _CHECKPOINT_MODE_ERRORS as exc:
        raise _checkpoint_mode_http_error(exc, thread_id) from exc
    except Exception:
        logger.exception("Failed to get checkpoint for thread %s", sanitize_log_param(thread_id))
        raise HTTPException(status_code=500, detail="Failed to get thread")

    if record is None and not checkpoint_id:
        raise HTTPException(status_code=404, detail=f"Thread {thread_id} not found")

    metadata = snapshot.metadata or {}
    if record is None:
        record = {
            "thread_id": thread_id,
            "status": "idle",
            "created_at": coerce_iso(snapshot.created_at or metadata.get("created_at", "")),
            "updated_at": coerce_iso(metadata.get("updated_at", snapshot.created_at or metadata.get("created_at", ""))),
            "metadata": {key: value for key, value in metadata.items() if key not in ("created_at", "updated_at", "step", "source", "writes", "parents")},
        }
    stored_status = record.get("status", "idle")
    status = _derive_thread_status(snapshot, pending_writes, fallback_status=stored_status) if checkpoint_id else stored_status

    return ThreadResponse(
        thread_id=thread_id,

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Read the 'Failed to get checkpoint for thread %s' traceback to see whether it is deserialization or connectivity.
  2. For deserialization failures, align the checkpointer/serializer versions with what wrote the data, or migrate old blobs.
  3. For connectivity, restore DB health; the GET is read-only and safe to retry.
  4. If one specific thread is corrupt, other threads remain readable — isolate and, if needed, delete the damaged thread.
Defensive patterns

Strategy: retry

Try / catch

try { const t = await api.get(`/api/threads/${id}`); }
catch (err) {
  if (err.status === 500) { await backoff(); retryOnce(); } // transient storage blips self-heal
  else throw err; // persistent 500 on one thread only => corrupt checkpoint, isolate it
}

Prevention

When it happens

Trigger: GET a thread whose checkpoint blob fails to deserialize (corrupt row, serializer version change), or while checkpoint storage is unreachable/locked.

Common situations: Upgrading langgraph-checkpoint without migrating blobs; truncated/corrupt checkpoint rows after a crash; DB connectivity blips; concurrent write holding a lock on checkpoint tables.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/365ddb78c84c3501. Report an issue: GitHub.