langchain-ai/deepagents · error · ValueError

a failed write cannot have changed the file

Error message

a failed write cannot have changed the file

What it means

WriteResult's `__post_init__` enforces that a failed write (ok=False) cannot report changed=True. If the file changed, the write partially succeeded, and claiming failure-with-change would corrupt callers' assumptions about whether the on-disk config matches the intended mutation.

Source

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

    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.

    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

View on GitHub (pinned to a1af029e6e)

Solutions

  1. If the file was actually updated, set ok=True and keep changed=True (optionally with no error)
  2. If the write truly failed, set changed=False and describe the failure in error
  3. For partial application, roll back or report the change via a distinct mechanism instead of contradictory flags

Example fix

// before
WriteResult(ok=False, error="rename failed", changed=True)
// after
WriteResult(ok=False, error="rename failed", changed=False)
Defensive patterns

Strategy: validation

Validate before calling

def check_write_flags(ok: bool, changed: bool) -> str | None:
    if changed and not ok:
        return "failed write cannot report changed=True"
    return None

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

Type guard

def is_consistent_write_result(ok: bool, changed: bool) -> bool:
    return not (changed and not ok)

Try / catch

try:
    result = WriteResult(ok=False, error=err, changed=changed)
except ValueError:
    result = WriteResult(ok=False, error=err, changed=False)

Prevention

When it happens

Trigger: Constructing WriteResult(ok=False, changed=True, error=<msg>) — e.g. a mutate callable returned True (made changes) but the overall write path was later marked failed, or hand-building a result in tests/tooling with contradictory fields.

Common situations: Partial-failure handling where the mutation applied but serialization/atomic rename failed; results aggregated from multiple attempts with mixed flags; test fixtures written by copy-paste.

Related errors


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