{"record":{"id":"828afb99ad387f92","repo":"langchain-ai/deepagents","slug":"a-failed-write-must-carry-an-error-detail","errorCode":null,"errorMessage":"a failed write must carry an error detail","messagePattern":"a failed write must carry an error detail","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/configuration/writer.py","lineNumber":44,"sourceCode":"\n    ok: bool\n    changed: bool\n    error: str | None = None\n\n    def __post_init__(self) -> None:\n        \"\"\"Reject outcomes that cannot describe a real transaction.\n\n        Callers branch on `ok` alone, so a failure with no detail would surface\n        as a bare \"could not be saved\" with nothing to act on, and a change\n        recorded against a failed write would report an edit that never\n        reached the file.\n\n        Raises:\n            ValueError: If the three fields do not describe one outcome.\n        \"\"\"\n        if not self.ok and self.error is None:\n            msg = \"a failed write must carry an error detail\"\n            raise ValueError(msg)\n        if self.changed and not self.ok:\n            msg = \"a failed write cannot have changed the file\"\n            raise ValueError(msg)\n        if self.ok and self.error is not None:\n            msg = \"a successful write cannot carry an error detail\"\n            raise ValueError(msg)\n\n\ndef update_user_config(\n    mutate: Callable[[dict[str, Any]], bool],\n    *,\n    config_path: Path | None = None,\n) -> WriteResult:\n    \"\"\"Serialize a read-modify-write of the user config and replace it atomically.\n\n    Writes the user tier only. The managed path is refused rather than trusted\n    to be unreachable.\n","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/configuration/writer.py#L26-L62","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nWriteResult(ok=False, error=None, changed=False)\n// after\nWriteResult(ok=False, error=\"permission denied writing ~/.config/deepagents/config.toml\", changed=False)","handlingStrategy":"try-catch","validationCode":"def check_write_result(ok: bool, error: str | None) -> str | None:\n    if not ok and error is None:\n        return \"failed write requires an error detail\"\n    return None\n\nissue = check_write_result(ok, err)  # check before constructing","typeGuard":"def is_valid_write_result(ok: bool, error: str | None) -> bool:\n    return ok or error is not None","tryCatchPattern":"try:\n    result = WriteResult(ok=False, error=None, changed=False)\nexcept ValueError:\n    result = WriteResult(ok=False, error=\"unknown write failure\", changed=False)","preventionTips":["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"],"tags":["invariant","configuration","file-write"],"backgroundTag":"inconsistent-write-result","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}