langchain-ai/deepagents · error · ValueError

Human decision count does not match pending approval calls

Error message

Human decision count does not match pending approval calls

What it means

Auto Mode's human-in-the-loop review requires the model/user to return exactly one decision for each tool call that is pending approval. This ValueError is raised in `_validate_human_decision_count` (called from `_human_review`) when `len(decisions) != len(calls)`, i.e. decisions were missing, duplicated, or extra. It guards the invariant that each pending approval call is resolved exactly once before execution proceeds.

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. Count the pending approval calls and ensure the decisions list has exactly one decision per call, in any order but with matching tool_call_ids.
  2. Deduplicate decisions for the same tool_call_id and add entries for any call that has no decision (default to reject if unsure).
  3. If resuming from an interrupt payload, pass the decisions straight from the user's responses rather than re-deriving them.

Example fix

// before
respond({ decisions: [ {tool_call_id: 'call_1', decision: 'approve'} ] })  // 2 calls pending
// after
respond({ decisions: [ {tool_call_id: 'call_1', decision: 'approve'}, {tool_call_id: 'call_2', decision: 'reject'} ] })
Defensive patterns

Strategy: validation

Validate before calling

pending_ids = {c['id'] for c in pending_calls}
dec_ids = [d['tool_call_id'] for d in decisions]
assert len(dec_ids) == len(set(dec_ids)), 'duplicate decisions'
assert set(dec_ids) == pending_ids, f'decisions {set(dec_ids)} != pending {pending_ids}'

Type guard

def has_decision_per_call(decisions: list[dict], calls: list[dict]) -> bool:
    ids = [d.get('tool_call_id') for d in decisions]
    return len(ids) == len(set(ids)) == len(calls) and set(ids) == {c['id'] for c in calls}

Try / catch

try:
    result = human_review(calls, decisions)
except ValueError as e:
    if 'decision count does not match' in str(e):
        # re-prompt the reviewer for the missing/duplicated calls
        decisions = re_prompt_missing(calls, decisions)
        result = human_review(calls, decisions)
    else:
        raise

Prevention

When it happens

Trigger: Calling `_human_review` with a decisions list whose length differs from the pending approval calls list — e.g. a human reviewer skipped an item, a UI submitted only approvals and omitted rejections, or duplicate decisions were included for one call id.

Common situations: Building a custom HITL front-end that submits partial decision sets; programmatically resuming an interrupted graph with a hand-built decisions array; a classifier or reviewer returning fewer/more entries than the batch of pending calls after a prompt or schema change.

Related errors


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