langchain-ai/deepagents · error · ValueError

Human decision count does not match Manual pending calls

Error message

Human decision count does not match Manual pending calls

What it means

Raised by `_validate_human_decision_count` inside `_human_review` when the number of human decisions supplied for a pending approval or Manual batch does not equal the number of pending tool calls in that batch. Auto mode requires a one-to-one decision per pending call to construct valid `ToolMessage` results; the sibling message 'does not match pending approval calls' is used when not in Manual mode.

Source

Thrown at libs/code/deepagents_code/auto_mode.py:1736

        return f"untrusted-{id(runtime):x}:{_batch_id(calls)}"
    return f"{thread_key}:{_batch_id(calls)}"


def _validate_human_decision_count(
    decisions: Sequence[object], calls: Sequence[ToolCall], *, manual: bool
) -> None:
    """Reject incomplete human responses before applying their decisions.

    Raises:
        ValueError: If the response has the wrong number of decisions.
    """
    if len(decisions) == len(calls):
        return
    if manual:
        msg = "Human decision count does not match Manual pending calls"
    else:
        msg = "Human decision count does not match pending approval calls"
    raise ValueError(msg)


def _resolved_tools(request: ModelRequest) -> dict[str, BaseTool]:
    return {
        tool.name: tool
        for tool in request.tools
        if isinstance(tool, BaseTool) and isinstance(tool.name, str)
    }


def _resolve_path(root: Path, raw: object) -> Path | None:
    """Return the absolute path a model-authored path argument names.

    The argument is untrusted model output, so expansion is part of what can
    fail: `Path.expanduser` raises `RuntimeError` for a `~name` prefix that
    names no account on this host. Expansion runs inside the guard for that
    reason, and every failure yields `None`.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Re-fetch the current pending calls and resubmit exactly one decision per call in batch order
  2. Discard stale decision payloads saved from a previous turn and capture fresh decisions against the current batch
  3. Check the approval frontend for UI-state races that let users submit before the batch renders fully
  4. If resuming from a checkpoint, regenerate the decision list from the checkpoint's pending calls rather than reusing old data

Example fix

// before: 1 decision for 2 pending calls
decisions = [{"type": "approve"}]

// after: one decision per pending call
decisions = [{"type": "approve"}, {"type": "reject", "reason": "not needed"}]
Defensive patterns

Strategy: validation

Validate before calling

def decisions_match_pending(decisions: list, pending_calls: list) -> bool:
    return len(decisions) == len(pending_calls)

Try / catch

try:
    resumed = agent.invoke(Command(resume=decisions), config)
except ValueError as exc:
    if "decision count does not match" in str(exc):
        decisions = fetch_fresh_pending_and_decide_again()
        resumed = agent.invoke(Command(resume=decisions), config)
    else:
        raise

Prevention

When it happens

Trigger: Resuming an interrupted turn with a decision list that is shorter or longer than the pending calls — e.g. submitting answers from a stale UI snapshot after the model proposed a different number of calls, replaying a saved receipt against a changed batch, or a frontend dropping decisions for some calls.

Common situations: UI state drift: user approves in one window while the agent re-proposed a different batch; resuming a checkpoint with a hardcoded decisions array; Manual-mode flows where the operator supplies decisions for only some calls; serialization bugs in custom approval frontends.

Related errors


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