langchain-ai/deepagents · error · ValueError

a successful write cannot carry an error detail

Error message

a successful write cannot carry an error detail

What it means

WriteResult's `__post_init__` enforces that a successful write (ok=True) must not carry an `error` detail. An error attached to a success would mislead callers and UIs that surface errors to users, so the combination is rejected at construction.

Source

Thrown at libs/code/deepagents_code/configuration/writer.py:50

        """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.

    A committed write to the default path also refreshes the shared process
    resolver, so later reads see the new value. That refresh is best-effort and
    never turns a landed write into a reported failure; see
    `refresh_shared_resolver`.

    Args:

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Set error=None when ok=True (the default)
  2. On successful retry, construct a fresh WriteResult instead of mutating/reusing the failed one
  3. Surface non-fatal warnings through a separate channel, not the error field

Example fix

// before
WriteResult(ok=True, error="previous attempt timed out", changed=True)
// after
WriteResult(ok=True, error=None, changed=True)
Defensive patterns

Strategy: validation

Validate before calling

def check_write_flags(ok: bool, error: str | None) -> str | None:
    if ok and error is not None:
        return "successful write must not carry an error detail"
    return None

issue = check_write_flags(ok, err)  # check before constructing

Type guard

def is_clean_success(ok: bool, error: str | None) -> bool:
    return not ok or error is None

Try / catch

try:
    result = WriteResult(ok=True, error=stale_error, changed=True)
except ValueError:
    result = WriteResult(ok=True, error=None, changed=True)

Prevention

When it happens

Trigger: Constructing WriteResult(ok=True, error=<msg>, ...) — e.g. code that always sets error from a previous attempt's exception without clearing it on retry success, or hand-built results in tests.

Common situations: Retry loops that keep the first attempt's exception object while succeeding on the second; copying a failed result and flipping ok to True; tests with fixture objects that include a leftover error message.

Related errors


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