bytedance/deer-flow · warning · HTTPException
request config configurable must be an object
Error message
request config configurable must be an object
What it means
HTTP 400 raised when the run's config.configurable is present but not an object — the checkpoint reference is valid, but the request config that must carry thread/checkpoint keys is malformed. The gateway needs a dict to attach thread_id, checkpoint_ns, checkpoint_id, and checkpoint_map into config.configurable.
Source
Thrown at backend/app/gateway/services.py:980
"checkpoint_ns": checkpoint_ns,
"checkpoint_id": str(checkpoint_id),
}
}
if checkpoint_map is not None:
read_config["configurable"]["checkpoint_map"] = checkpoint_map
checkpointer = get_checkpointer(request)
try:
checkpoint_tuple = await checkpointer.aget_tuple(read_config)
except Exception as exc:
logger.exception("Failed to validate checkpoint %s for thread %s", checkpoint_id, sanitize_log_param(thread_id))
raise HTTPException(status_code=500, detail="Failed to validate checkpoint") from exc
if checkpoint_tuple is None:
raise HTTPException(status_code=404, detail=f"Checkpoint {checkpoint_id} not found")
configurable = config.setdefault("configurable", {})
if not isinstance(configurable, dict):
raise HTTPException(status_code=400, detail="request config configurable must be an object")
configurable["thread_id"] = thread_id
configurable["checkpoint_ns"] = checkpoint_ns
configurable["checkpoint_id"] = str(checkpoint_id)
if checkpoint_map is not None:
configurable["checkpoint_map"] = checkpoint_map
async def ensure_checkpoint_history_seeded(
request: Request,
*,
thread_id: str,
assistant_id: str | None,
) -> None:
"""Backfill an empty run-event feed from an existing checkpoint head.
No-op unless the feed is empty AND a checkpoint head with messages
exists — i.e. a legacy checkpoint-only thread facing its first journaled
run. This is a migration shim: remove it once pre-journal threads are noView on GitHub (pinned to 1dd6ba1acb)
Solutions
- Make config.configurable a JSON object: {"configurable": {"thread_id": "..."}}.
- Or omit config entirely when you have nothing to put in configurable — the gateway sets the required keys itself.
Example fix
# before
{"config": {"configurable": "t1"}, "checkpoint_id": "cp1", "input": {...}}
# after
{"config": {"configurable": {"thread_id": "t1"}}, "checkpoint_id": "cp1", "input": {...}} Defensive patterns
Strategy: type-guard
Validate before calling
const cfg = payload.config?.configurable;
if (cfg !== undefined && (typeof cfg !== 'object' || cfg === null || Array.isArray(cfg))) {
throw new TypeError('config.configurable must be an object');
} Type guard
const isConfigurable = (c: unknown): c is Record<string, unknown> => typeof c === 'object' && c !== null && !Array.isArray(c);
Prevention
- Omit config.configurable entirely when there is nothing to configure.
- Type the run payload so configurable is Record<string, unknown> | undefined.
When it happens
Trigger: Sending "config": {"configurable": "thread-123"} or configurable: ["a","b"] together with a checkpoint reference.
Common situations: Clients modeling configurable as a string thread id (a plausible shorthand); passing a serialized JSON string of the configurable object; schema confusion between config.configurable and config.metadata.
Related errors
- checkpoint must be an object
- checkpoint thread_id does not match request thread_id
- Unknown SSO provider: {provider}
- Cannot set extra config key '{key}' to masked value '***'; p
- MCP server '{server_name}' with stdio transport requires a c
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/74465e5807354f0e.
Report an issue: GitHub.