langchain-ai/deepagents · error · TypeError

context.hooks_server_events must be a list of strings or nul

Error message

context.hooks_server_events must be a list of strings or null.

What it means

`context.hooks_server_events` lists which hook server events the offload operation subscribes to, and must be a list of strings or null. The validator rejects non-list values and lists containing non-string elements (e.g. ints or dicts) to guarantee a clean JSON contract with the hook server.

Source

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

        msg = (
            "context.model_context_limit must be an integer or null, "
            f"got {type(limit).__name__}."
        )
        raise TypeError(msg)
    auto_approve = context.get("auto_approve")
    if auto_approve is not None and not isinstance(auto_approve, bool):
        msg = (
            f"context.auto_approve must be a boolean or null, "
            f"got {type(auto_approve).__name__}."
        )
        raise TypeError(msg)
    events = context.get("hooks_server_events")
    if events is not None and (
        not isinstance(events, list)
        or any(not isinstance(event, str) for event in events)
    ):
        msg = "context.hooks_server_events must be a list of strings or null."
        raise TypeError(msg)


def _checkpoint_id(state: Mapping[str, object]) -> str:
    checkpoint = state.get("checkpoint")
    value = checkpoint.get("checkpoint_id") if isinstance(checkpoint, Mapping) else None
    if not isinstance(value, str) or not value:
        msg = "The thread has no checkpoint to offload."
        raise _OffloadConflictError(msg)
    return value


def _operation_payload(
    payload: object,
) -> tuple[str, dict[str, Any], dict[str, object]]:
    """Validate the narrow client-to-operation request shape.

    Args:
        payload: Decoded request JSON.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Split comma-separated config strings: [e.strip() for e in raw.split(',') if e.strip()].
  2. Wrap single values in a list: events = [raw] if isinstance(raw, str) else list(raw).
  3. Cast elements to str: [str(e) for e in events], and drop or fix non-string entries.
  4. Pass None (or omit the key) when no hook events should be requested.

Example fix

// before
context = {"hooks_server_events": "checkpoint.completed,thread.archived"}
// after
raw = "checkpoint.completed,thread.archived"
context = {"hooks_server_events": [e.strip() for e in raw.split(",") if e.strip()]}
Defensive patterns

Strategy: validation

Validate before calling

def validate_hooks_server_events(value):
    if value is None:
        return True
    return isinstance(value, list) and all(isinstance(e, str) for e in value)
# call before offload: assert validate_hooks_server_events(ctx.get("hooks_server_events"))

Type guard

def is_str_list_or_none(value) -> bool:
    return value is None or (isinstance(value, list) and all(isinstance(e, str) for e in value))

Try / catch

try:
    state = await offload(thread_id, payload)
except TypeError as exc:
    if "hooks_server_events" in str(exc):
        raw = payload["context"].get("hooks_server_events")
        if isinstance(raw, str):
            raw = [e.strip() for e in raw.split(",") if e.strip()]
        payload["context"]["hooks_server_events"] = [str(e) for e in (raw or [])]
        state = await offload(thread_id, payload)
    else:
        raise

Prevention

When it happens

Trigger: Calling offload() with context={'hooks_server_events': 'checkpoint'} (bare string), {'hooks_server_events': [1, 2]}, {'hooks_server_events': ('a','b')} (tuple), or {'hooks_server_events': ['ok', 42]}.

Common situations: Passing a comma-separated string from config instead of splitting it, building the list from enum values or ints, using a tuple/set from internal code, or JSON round-tripping that mixed types into the array.

Related errors


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