langchain-ai/deepagents · error · ValueError
a failed write must carry an error detail
Error message
a failed write must carry an error detail
What it means
WriteResult's `__post_init__` (writer.py) enforces outcome consistency: a result with ok=False must carry an `error` detail explaining the failure. Returning a bare failure with no error would leave callers and users without any diagnosis, so it is rejected at construction time.
Source
Thrown at libs/code/deepagents_code/configuration/writer.py:44
ok: bool
changed: bool
error: str | None = None
def __post_init__(self) -> None:
"""Reject outcomes that cannot describe a real transaction.
Callers branch on `ok` alone, so a failure with no detail would surface
as a bare "could not be saved" with nothing to act on, and a change
recorded against a failed write would report an edit that never
reached the file.
Raises:
ValueError: If the three fields do not describe one outcome.
"""
if not self.ok and self.error is None:
msg = "a failed write must carry an error detail"
raise ValueError(msg)
if self.changed and not self.ok:
msg = "a failed write cannot have changed the file"
raise ValueError(msg)
if self.ok and self.error is not None:
msg = "a successful write cannot carry an error detail"
raise ValueError(msg)
def update_user_config(
mutate: Callable[[dict[str, Any]], bool],
*,
config_path: Path | None = None,
) -> WriteResult:
"""Serialize a read-modify-write of the user config and replace it atomically.
Writes the user tier only. The managed path is refused rather than trusted
to be unreachable.
View on GitHub (pinned to a1af029e6e)
Solutions
- Populate `error` with a descriptive message whenever ok=False (e.g. str(exc) or a specific reason)
- Where an exception is caught, convert it to the error field: WriteResult(ok=False, error=str(exc), changed=False)
- If the write actually succeeded, set ok=True instead of ok=False with no error
Example fix
// before WriteResult(ok=False, error=None, changed=False) // after WriteResult(ok=False, error="permission denied writing ~/.config/deepagents/config.toml", changed=False)
Defensive patterns
Strategy: try-catch
Validate before calling
def check_write_result(ok: bool, error: str | None) -> str | None:
if not ok and error is None:
return "failed write requires an error detail"
return None
issue = check_write_result(ok, err) # check before constructing Type guard
def is_valid_write_result(ok: bool, error: str | None) -> bool:
return ok or error is not None Try / catch
try:
result = WriteResult(ok=False, error=None, changed=False)
except ValueError:
result = WriteResult(ok=False, error="unknown write failure", changed=False) Prevention
- Always convert caught exceptions into the error field: error=str(exc)
- Never construct ok=False results without a reason string
- Let update_user_config build WriteResult instead of hand-constructing it
When it happens
Trigger: Constructing WriteResult(ok=False, error=None, ...) directly — e.g. building the result in custom tooling/tests, or a code path that catches an exception but forgets to convert it to an error string before constructing the result.
Common situations: Wrapping update_user_config in tests with hand-built results; a custom mutate callable wrapper that swallows exceptions and returns ok=False without populating error; refactoring that dropped the error field population.
Related errors
- a failed write cannot have changed the file
- a successful write cannot carry an error detail
- a {self.status.health.value} snapshot must carry no data; an
- {what} must be absolute: {path}
- Could not determine the home directory: set $HOME, or set DE
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/828afb99ad387f92.
Report an issue: GitHub.