{"record":{"id":"e9223a0f05942795","repo":"langchain-ai/deepagents","slug":"langgraph-returned-a-non-list-messages-channel","errorCode":null,"errorMessage":"LangGraph returned a non-list messages channel.","messagePattern":"LangGraph returned a non-list messages channel\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/offload_api.py","lineNumber":557,"sourceCode":"    \"\"\"Hydrate serialized checkpoint messages for the compaction service.\n\n    Args:\n        values: State values returned by LangGraph Server.\n\n    Returns:\n        A shallow state copy containing LangChain message objects.\n\n    Raises:\n        TypeError: If the server returns an unexpected state shape.\n    \"\"\"\n    if not isinstance(values, dict):\n        msg = \"LangGraph returned non-object thread state.\"\n        raise TypeError(msg)\n    state = dict(values)\n    messages = state.get(\"messages\", [])\n    if not isinstance(messages, list):\n        msg = \"LangGraph returned a non-list messages channel.\"\n        raise TypeError(msg)\n    state[\"messages\"] = convert_to_messages(messages)\n\n    # LangGraph serializes the summary stored inside the private event channel\n    # independently of the top-level `messages` channel. The summarization SDK\n    # prepends it to the effective conversation, so it must be a message object\n    # too rather than the serialized dict returned by the thread API.\n    event = state.get(\"_summarization_event\")\n    if isinstance(event, Mapping) and \"summary_message\" in event:\n        hydrated_event = dict(event)\n        summary_message = hydrated_event[\"summary_message\"]\n        hydrated_event[\"summary_message\"] = convert_to_messages([summary_message])[0]\n        state[\"_summarization_event\"] = hydrated_event\n    return cast(\"_OffloadState\", state)\n\n\ndef _checkpoint_model_context(\n    context: dict[str, Any], state: Mapping[str, object]\n) -> dict[str, Any]:","sourceCodeStart":539,"sourceCodeEnd":575,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/offload_api.py#L539-L575","documentation":"The hydrated thread state's `messages` channel must be a list; the SDK converts each entry to a message object via convert_to_messages. If LangGraph reports a non-list messages channel, the state shape is invalid and _hydrate_state raises this TypeError instead of iterating over something that cannot be converted. Like the non-object state error, it points to a server/runtime or state-corruption problem.","triggerScenarios":"_execute_offload hydrating state where state.get('messages') came back as a dict, string, or None (key present with wrong type) instead of a list.","commonSituations":"A thread server storing messages in a legacy/custom channel format, a manually edited or corrupted checkpoint where messages was overwritten, an incompatible graph definition whose 'messages' channel is not a list-typed channel, or a mock returning the wrong shape in tests.","solutions":["Check the thread's checkpoint/state integrity; recover from an earlier checkpoint or start a new thread if messages is corrupted.","Ensure the deployed graph uses the standard add_messages/messages list channel the SDK expects; align graph and SDK versions.","Log the raw get_state values to see the actual channel type; fix or migrate the stored state.","In tests, make fake get_state return {'messages': [], ...} as a dict of channels."],"exampleFix":"// before\nvalues = await client.threads.get_state(thread_id)  # returns {'messages': {'0': {...}}}\n// after\n# repair/migrate the channel before offloading\nif not isinstance(values.get(\"messages\"), list):\n    values[\"messages\"] = list(values[\"messages\"].values())\nstate = await offload(thread_id, payload)","handlingStrategy":"try-catch","validationCode":"state = await client.threads.get_state(thread_id)\nmessages = state.get(\"messages\", []) if isinstance(state, dict) else []\nif not isinstance(messages, list):\n    raise RuntimeError(f\"Thread {thread_id} has a corrupt 'messages' channel\")","typeGuard":"def has_message_list(state) -> bool:\n    return isinstance(state, dict) and isinstance(state.get(\"messages\", []), list)","tryCatchPattern":"try:\n    state = await offload(thread_id, payload)\nexcept TypeError as exc:\n    if \"non-list messages channel\" in str(exc):\n        raise RuntimeError(\n            f\"Thread {thread_id} 'messages' channel is corrupt; recover from an earlier checkpoint\"\n        ) from exc\n    raise","preventionTips":["Use the standard messages list channel (add_messages) in your graph definition","Avoid hand-editing checkpoints or channel values","Keep graph and SDK versions aligned so channel types match expectations","In tests, make get_state fakes return {'messages': [], ...} with correct shapes"],"tags":["langgraph","thread-state","messages","type-error"],"backgroundTag":"unexpected-server-response-shape","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}