langchain-ai/deepagents · error · ValueError
phase must be one of {sorted(_ATTEMPT_PHASES)}, got {phase!r
Error message
phase must be one of {sorted(_ATTEMPT_PHASES)}, got {phase!r} What it means
build_attempt_event emits a model_attempt lifecycle event whose phase must be one of _ATTEMPT_PHASES = frozenset({"start", "complete"}) (model_retry.py:133). An unknown phase raises ValueError('phase must be one of [\'complete\', \'start\'], got {phase!r}'), keeping attempt-event consumers able to rely on a fixed phase vocabulary.
Source
Thrown at libs/code/deepagents_code/model_retry.py:950
def build_attempt_event(call_id: str, attempt: int, *, phase: str) -> dict[str, object]:
"""Build the custom-stream payload marking one model attempt boundary.
Args:
call_id: Opaque ID shared by every attempt of one model call.
attempt: The 0-indexed attempt whose boundary is marked.
phase: `"start"` before the handler runs, `"complete"` after it
returns successfully.
Returns:
A stream-writer payload consumed by the client renderers.
Raises:
ValueError: If `phase` is not a known lifecycle phase.
"""
if phase not in _ATTEMPT_PHASES:
msg = f"phase must be one of {sorted(_ATTEMPT_PHASES)}, got {phase!r}"
raise ValueError(msg)
return {
"type": "model_attempt",
"phase": phase,
"call_id": call_id,
"attempt": attempt,
}
def _validated_call_id(value: object) -> str | None:
"""Return `value` as a correlation ID, or `None` when it is untrusted."""
if (
not isinstance(value, str)
or not 1 <= len(value) <= _CALL_ID_MAX_LENGTH
or any(char not in _CALL_ID_CHARS for char in value)
):
return None
return value
View on GitHub (pinned to a1af029e6e)
Solutions
- Use exactly "start" or "complete" for the phase argument.
- Normalize labels before calling: map application states to the two supported phases.
- If a new phase is genuinely needed, check whether the library version supports it or propose the change upstream — do not pass ad-hoc strings.
Example fix
// before build_attempt_event(phase="begin", call_id="call-1", attempt=1) # ValueError // after build_attempt_event(phase="start", call_id="call-1", attempt=1)
Defensive patterns
Strategy: type-guard
Validate before calling
PHASES = {"start", "complete"}
if phase not in PHASES:
phase = "complete" if phase in {"finish", "end", "done"} else "start" Type guard
from typing import Literal, TypeGuard
Phase = Literal["start", "complete"]
def is_phase(value: object) -> TypeGuard[Phase]:
return value in ("start", "complete") Try / catch
try:
event = build_attempt_event(phase=phase, call_id=call_id, attempt=attempt)
except ValueError as exc:
logging.warning("%s; skipping attempt event", exc) Prevention
- Annotate the phase parameter as Literal['start','complete'] in your wrappers so mypy/pyright reject bad values.
- Centralize event building in one helper instead of scattering literal strings.
- Map application lifecycle states to the two supported phases at the instrumentation boundary.
When it happens
Trigger: Calling build_attempt_event with phase values like "begin", "finish", "error", "retry", or any casing variant ("Start"), since membership in the frozenset is exact-match.
Common situations: Inventing new lifecycle phases when instrumenting model calls; renaming phases in application code without updating the event builder; passing a human-readable label instead of the canonical phase string.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Invalid recent startup mode: {mode!r}
- Invalid sort_order {sort_order!r}; expected 'updated_at' or
- Invalid scope {scope!r}; expected 'cwd' or 'all'
- call_id and failed_attempt must be provided together
- modes can only be provided when agent is a factory
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/58d04bf1ba440e2e.
Report an issue: GitHub.