langchain-ai/deepagents · error · ValueError
deny decisions require a reason
Error message
deny decisions require a reason
What it means
A pydantic `model_validator(mode="after")` on `AutoDecision` enforces that any decision equal to "deny" carries a non-blank `reason`. This guarantees the user always sees why an action was blocked in auto mode; a deny without a reason is considered invalid and rejected at parse time.
Source
Thrown at libs/code/deepagents_code/auto_mode.py:285
tool_call_id: str
decision: Literal["allow", "deny"]
category: AutoDecisionCategory
reason: str
@field_validator("tool_call_id")
@classmethod
def _nonempty_id(cls, value: str) -> str:
if not value:
msg = "tool_call_id must not be empty"
raise ValueError(msg)
return value
@model_validator(mode="after")
def _denial_has_reason(self) -> AutoDecision:
if self.decision == "deny" and not self.reason.strip():
msg = "deny decisions require a reason"
raise ValueError(msg)
return self
class AutoDecisionBatch(BaseModel):
"""Validated classifier response for one unresolved action batch."""
model_config = ConfigDict(extra="forbid")
decisions: list[AutoDecision]
class AutoModeCounters(TypedDict):
"""Server-owned denial and availability counters for one thread."""
consecutive_denials: int
total_denials: int
consecutive_unavailable: int
last_batch_id: str | NoneView on GitHub (pinned to a1af029e6e)
Solutions
- Include a human-readable reason string whenever the decision is "deny".
- Update the classifier prompt to require a reason for denies (few-shot example with deny + reason).
- Pre-validate raw responses: if decision == "deny" and reason is blank, retry the classification or fall back to prompting the user.
- Normalize whitespace-only reasons by rejecting and re-asking the model rather than passing them through.
Example fix
// before
AutoDecision(decision="deny", reason="") # ValueError
// after
reason = raw.get("reason") or "No reason provided by classifier"
AutoDecision(decision="deny", reason=reason) Defensive patterns
Strategy: validation
Validate before calling
def _safe_decision(raw: dict) -> AutoDecision | None:
if raw.get("decision") == "deny" and not str(raw.get("reason", "")).strip():
logger.warning("deny without reason: %r", raw)
return None # retry classification or escalate to the user
return AutoDecision(**raw) Type guard
def _deny_has_reason(raw: object) -> TypeGuard[dict[str, str]]:
return (
isinstance(raw, dict)
and (raw.get("decision") != "deny" or bool(str(raw.get("reason", "")).strip()))
) Try / catch
try:
decision = AutoDecision(**raw)
except pydantic.ValidationError as exc:
if any("deny decisions require a reason" in e["msg"] for e in exc.errors()):
decision = reclassify_with_reason_required(raw)
else:
raise Prevention
- Instruct the classifier to always include a reason for denies; show a few-shot example.
- Require `reason` in the JSON schema passed to the model for structured output.
- Normalize blank reasons by retrying or substituting an explicit escalation to manual confirmation.
- Review prompt changes that touch decision output for the reason requirement.
When it happens
Trigger: Constructing AutoDecision(decision="deny", reason="") or reason=" ", or parsing an LLM classifier response where a deny decision omits the reason field.
Common situations: The classifier emitted `{"decision": "deny"}` with no reason; prompt changes dropped the reason requirement; whitespace-only reasons from truncated model output.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- tool_call_id must not be empty
- Classifier result did not contain exactly one decision per r
- trusted thread, turn, and tool-call identity are required
- Auto mode requires every proposed tool call to have an ID
- Auto mode rejects action batches with duplicate tool-call ID
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/1e8e7e2f08efc57b.
Report an issue: GitHub.