langchain-ai/deepagents · error · TypeError

hook_responses must be a JSON object.

Error message

hook_responses must be a JSON object.

What it means

`hook_responses` holds replies to any pending hook questions and must be a JSON object (dict). The field defaults to {} when absent, but if explicitly provided it must be a dict; lists, strings, or None are rejected. Keys are later normalized to strings ({str(key): value}).

Source

Thrown at libs/code/deepagents_code/offload_api.py:528

    Raises:
        TypeError: If the payload or a structured field has the wrong shape.
    """
    if not isinstance(payload, dict):
        msg = "Offload request must be a JSON object."
        raise TypeError(msg)
    operation_id = payload.get("operation_id")
    context = payload.get("context")
    responses = payload.get("hook_responses", {})
    if not isinstance(operation_id, str) or not operation_id:
        msg = "operation_id must be a non-empty string."
        raise TypeError(msg)
    if not isinstance(context, dict):
        msg = "context must be a JSON object."
        raise TypeError(msg)
    if not isinstance(responses, dict):
        msg = "hook_responses must be a JSON object."
        raise TypeError(msg)
    validated_context = {str(key): value for key, value in context.items()}
    _validate_context(validated_context)
    return (
        operation_id,
        _strip_transport_model_params(validated_context),
        {str(key): value for key, value in responses.items()},
    )


def _hydrate_state(values: object) -> _OffloadState:
    """Hydrate serialized checkpoint messages for the compaction service.

    Args:
        values: State values returned by LangGraph Server.

    Returns:
        A shallow state copy containing LangChain message objects.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Key responses by hook id: {'<hook-id>': <answer>} as a dict, not a list.
  2. Omit the key entirely or pass {} when there are no hook responses — never None.
  3. If responses arrive as JSON text, json.loads() them and confirm the result is a dict.
  4. If you have a list of {id, answer} records, convert: {r['id']: r['answer'] for r in records}.

Example fix

// before
payload = {"operation_id": "op1", "context": {}, "hook_responses": None}
// after
payload = {"operation_id": "op1", "context": {}, "hook_responses": {}}
Defensive patterns

Strategy: validation

Validate before calling

def ensure_hook_responses(payload):
    responses = payload.get("hook_responses", {})
    if responses is None:
        responses = {}
    if not isinstance(responses, dict):
        raise ValueError("hook_responses must be a dict keyed by hook id")
    payload["hook_responses"] = {str(k): v for k, v in responses.items()}
    return payload

Type guard

def has_valid_hook_responses(payload) -> bool:
    responses = payload.get("hook_responses", {})
    return responses is None or isinstance(responses, dict)

Try / catch

try:
    state = await offload(thread_id, payload)
except TypeError as exc:
    if "hook_responses" in str(exc):
        payload["hook_responses"] = {}
        state = await offload(thread_id, payload)
    else:
        raise

Prevention

When it happens

Trigger: Calling offload() with payload={'hook_responses': None}, {'hook_responses': [{'hook_id': ..., 'answer': ...}]}, or {'hook_responses': 'response-json'}.

Common situations: Passing a list of response records instead of a mapping keyed by hook/question id, serializing the responses dict to a string before embedding, passing None for 'no responses' instead of omitting the key or using {}.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/91faa3d46fdcf89c. Report an issue: GitHub.