{"record":{"id":"010ba07fe2016ee0","repo":"langchain-ai/deepagents","slug":"thread-id-is-required-in-config-configurable","errorCode":null,"errorMessage":"thread_id is required in config.configurable","messagePattern":"thread_id is required in config\\.configurable","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/client/remote_client.py","lineNumber":166,"sourceCode":"    return cast(\"OffloadResult\", result)\n\n\ndef _require_thread_id(config: Mapping[str, Any] | None) -> str:\n    \"\"\"Extract and validate that `thread_id` is present in config.\n\n    Args:\n        config: Config dict with `configurable.thread_id`.\n\n    Returns:\n        The thread ID string.\n\n    Raises:\n        ValueError: If `thread_id` is missing.\n    \"\"\"\n    thread_id = (config or {}).get(\"configurable\", {}).get(\"thread_id\")\n    if not thread_id:\n        msg = \"thread_id is required in config.configurable\"\n        raise ValueError(msg)\n    return thread_id\n\n\ndef state_has_pending_work(state: object) -> bool:\n    \"\"\"Return whether a checkpoint snapshot still holds unfinished graph work.\n\n    Single definition of \"pending\" shared by the app-side detector and the\n    post-recovery verification, so the two cannot drift apart.\n\n    Args:\n        state: A `StateSnapshot`-shaped object, or `None`.\n\n    Returns:\n        Whether the snapshot has a queued node, task, or interrupt.\n    \"\"\"\n    if state is None:\n        return False\n    return bool(","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/client/remote_client.py#L148-L184","documentation":"RemoteAgent methods (`aoffload`, `astream`, `aget_state`, `acancel_active_runs`, `aupdate_state`, `aabandon_pending_work`) require a LangGraph thread id to know which server-side thread to operate on. `_require_thread_id` extracts `config['configurable']['thread_id']` and raises ValueError when the config is None, missing `configurable`, or has an empty/falsy `thread_id`. The thread id is mandatory because every underlying call targets `/threads/{thread_id}/...` on the server.","triggerScenarios":"Calling any RemoteAgent method with `config=None`, `config={}`, `config={'configurable': {}}`, or `config={'configurable': {'thread_id': ''}}` (or None).","commonSituations":"Forgetting to thread the checkpointer-assigned thread id through a LangGraph `RunnableConfig`; constructing a fresh config dict by hand instead of copying one the framework produced; building a client wrapper that drops `configurable` when it sanitizes configs; passing a config from a non-LangGraph code path that uses different keys.","solutions":["Build the config as `{'configurable': {'thread_id': <id>}}` with a non-empty string id before calling any RemoteAgent method.","If the thread id comes from a checkpointer or session manager, copy its config (`config.copy()` keeps `configurable`) rather than constructing a new dict.","Validate the config early in your own wrapper: check `config.get('configurable', {}).get('thread_id')` and fail with a clear message at your boundary.","If you have no thread id yet, create/register the thread first (e.g. via the server's thread APIs or `aensure_thread`) and then pass its id."],"exampleFix":"// before\nawait agent.astream(input, config={})\n\n// after\nconfig = {\"configurable\": {\"thread_id\": thread_id}}\nawait agent.astream(input, config=config)","handlingStrategy":"validation","validationCode":"def has_thread_id(config) -> bool:\n    return bool((config or {}).get(\"configurable\", {}).get(\"thread_id\"))\n\nif not has_thread_id(config):\n    raise ValueError(\"config.configurable.thread_id must be set before calling RemoteAgent\")","typeGuard":"def is_runnable_config(config: object) -> bool:\n    return (\n        isinstance(config, dict)\n        and isinstance(config.get(\"configurable\"), dict)\n        and isinstance(config[\"configurable\"].get(\"thread_id\"), str)\n        and bool(config[\"configurable\"][\"thread_id\"])\n    )","tryCatchPattern":"try:\n    await agent.astream(input, config=config)\nexcept ValueError as exc:\n    if \"thread_id is required\" in str(exc):\n        logging.error(\"No thread id in config.configurable; create/register a thread first\")\n    else:\n        raise","preventionTips":["Always derive configs from the checkpointer/framework (`config.copy()`) instead of building fresh dicts.","Add a tiny helper that constructs `{\"configurable\": {\"thread_id\": id}}` and use it everywhere.","Assert thread id presence in tests that exercise RemoteAgent calls."],"tags":["config","validation","thread-id","langgraph"],"backgroundTag":"missing-config-key","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}