langchain-ai/deepagents · error · ValueError
An offload {status!r} result must carry a reason.
Error message
An offload {status!r} result must carry a reason. What it means
`unchanged_offload_result` builds the wire result for non-compacting offload outcomes. Because the flat wire shape types `error` as `str | None` for every status, a `denied` or `failed` result without a reason cannot be caught by the type checker, so this constructor enforces it at runtime: refusing without a reason would render as the client's generic 'server rejected the operation' message. Raises ValueError when status is denied/failed and error is falsy.
Source
Thrown at libs/code/deepagents_code/offload_middleware.py:674
status: A non-compacting outcome.
messages: Messages left in the conversation.
tokens: Context estimate, unchanged by definition.
error: Reason, required for `denied` and `failed`.
Returns:
Typed result containing unchanged context statistics.
Raises:
ValueError: If a refusal carries no reason.
"""
if status in {"denied", "failed"} and not error:
# `error` is `str | None` on every status because the wire shape is one
# flat object, so the checker cannot make "a refusal has a reason" a
# compile-time fact. Enforce it at the single construction point
# instead: a reasonless refusal renders as the client's generic "the
# server rejected the operation", which tells the user nothing.
msg = f"An offload {status!r} result must carry a reason."
raise ValueError(msg)
return {
"status": status,
"messages_offloaded": 0,
"messages_kept": messages,
"tokens_before": tokens,
"tokens_after": tokens,
"archive_path": None,
"archive_ephemeral": False,
"error": error,
}
class OffloadCompleteResponse(TypedDict):
"""Wire response for an attempt that finished without needing the client."""
status: Literal["complete"]
result: OffloadResult
View on GitHub (pinned to a1af029e6e)
Solutions
- Pass a user-meaningful `error` string when building denied/failed results.
- Use a status that permits no error (e.g. a complete/no-op status) if nothing was refused.
- Route all refusal construction through `unchanged_offload_result` so the check stays in one place.
Example fix
// before
result = unchanged_offload_result("denied", messages=3, tokens=900)
// after
result = unchanged_offload_result("denied", messages=3, tokens=900, error="offload disabled for this thread") Defensive patterns
Strategy: validation
Validate before calling
assert status not in {"denied", "failed"} or error, f"{status!r} requires an error reason" Type guard
def refusal_has_reason(status: OffloadStatus, error: str | None) -> bool:
return status not in ("denied", "failed") or bool(error) Try / catch
try:
result = unchanged_offload_result(status, messages=m, tokens=t, error=reason)
except ValueError:
result = unchanged_offload_result(status, messages=m, tokens=t, error=DEFAULT_DENIAL_REASON) Prevention
- Always construct refusal results through unchanged_offload_result, never as raw dicts.
- Give every denial path a concrete, user-readable reason string.
- Add a table-driven test covering each status with/without error.
When it happens
Trigger: Calling `unchanged_offload_result("denied", messages=..., tokens=...)` or with "failed" without passing `error=`; HTTP-boundary code building a refusal result without a reason string.
Common situations: Custom server middleware denying offloads (rate limits, auth) without setting a reason; tests or handlers constructing refusal results directly.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- Offload server completed without a typed result.
- Offload result has no status.
- Offload result field {field!r} must be an integer, got {type
- context.{key} must be a string or null, got {type(value).__n
- context.{key} must be an object, got {type(value).__name__}.
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/16ed458a3998cd77.
Report an issue: GitHub.