{"record":{"id":"7db233fb1219912f","repo":"langchain-ai/deepagents","slug":"readresult-total-lines-requires-start-line-and-end","errorCode":null,"errorMessage":"ReadResult.total_lines requires start_line and end_line to be set","messagePattern":"ReadResult\\.total_lines requires start_line and end_line to be set","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/deepagents/deepagents/backends/protocol.py","lineNumber":261,"sourceCode":"        immediately after the last one shown (`next_offset == end_line`, since\n        `end_line` is 1-indexed). Fail loudly here to keep a backend from\n        emitting a `next_offset` that would silently skip unshown source lines\n        once it reaches the middleware.\n        \"\"\"\n        if (self.start_line is None) != (self.end_line is None):\n            msg = \"ReadResult.start_line and end_line must be set together or both left unset\"\n            raise ValueError(msg)\n        if self.no_lines_requested and (\n            self.error is not None or self.start_line is not None or self.next_offset is not None or self.total_lines is not None\n        ):\n            msg = \"ReadResult.no_lines_requested describes an uninspected window; it cannot be combined with error or pagination fields\"\n            raise ValueError(msg)\n        if self.next_offset is not None and self.start_line is None:\n            msg = \"ReadResult.next_offset requires start_line and end_line to be set\"\n            raise ValueError(msg)\n        if self.total_lines is not None and self.start_line is None:\n            msg = \"ReadResult.total_lines requires start_line and end_line to be set\"\n            raise ValueError(msg)\n\n        # Numeric consistency of a present window. `start_line`/`end_line` are\n        # bound together above, so testing `start_line` covers both.\n        if self.start_line is not None and self.end_line is not None:\n            if self.start_line < 1 or self.end_line < self.start_line:\n                msg = f\"ReadResult window must satisfy 1 <= start_line <= end_line, got start_line={self.start_line}, end_line={self.end_line}\"\n                raise ValueError(msg)\n            if self.total_lines is not None and self.total_lines < self.end_line:\n                msg = f\"ReadResult.total_lines ({self.total_lines}) cannot be less than end_line ({self.end_line})\"\n                raise ValueError(msg)\n            if self.next_offset is not None and self.next_offset != self.end_line:\n                msg = f\"ReadResult.next_offset ({self.next_offset}) must equal end_line ({self.end_line}), the 0-indexed line after the last shown\"\n                raise ValueError(msg)\n\n\n@dataclass\nclass WriteResult:\n    \"\"\"Result from backend `write` operations.","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/deepagents/deepagents/backends/protocol.py#L243-L279","documentation":"ReadResult.__post_init__ validates that any backend populating `total_lines` on a paginated read also populates the window fields `start_line` and `end_line`. `total_lines` is only meaningful relative to a known window, so a dataclass built with total_lines but no line window violates the protocol invariant and is rejected at construction time with a ValueError. This is a contract check that keeps ReadResult self-consistent for downstream line-number formatting and pagination logic.","triggerScenarios":"Constructing ReadResult(content=..., total_lines=120) without setting start_line/end_line. Typically a custom backend's `read` implementation returns total_lines to report file size but omits the window fields, or builds the result via a helper that only forwards some fields.","commonSituations":"Writing a custom Backend subclass implementing `read`; refactoring a backend after the protocol added the line-window fields; hand-building ReadResult in tests with partial fields.","solutions":["Set start_line and end_line on the ReadResult whenever you set total_lines (1-based, inclusive window).","If the read was unpaginated, compute the window from the content (start_line=1, end_line=number of lines returned) instead of dropping the fields.","If you do not have window metadata, omit total_lines entirely rather than returning it alone."],"exampleFix":"// before\nreturn ReadResult(content=text, total_lines=len(all_lines))\n// after\nreturn ReadResult(\n    content=text,\n    start_line=1,\n    end_line=len(lines_returned),\n    total_lines=len(all_lines),\n)","handlingStrategy":"validation","validationCode":"def is_valid_read_result_fields(**fields):\n    if fields.get('total_lines') is not None:\n        return fields.get('start_line') is not None and fields.get('end_line') is not None\n    return True\n# call before constructing: is_valid_read_result_fields(total_lines=120, start_line=None, end_line=None) -> False","typeGuard":"def has_consistent_window(r) -> bool:\n    return not (r.total_lines is not None and (r.start_line is None or r.end_line is None))","tryCatchPattern":"try:\n    result = ReadResult(content=text, total_lines=n)\nexcept ValueError as e:\n    logger.warning('invalid ReadResult: %s', e)\n    result = ReadResult(content=text, start_line=1, end_line=len(text.splitlines()), total_lines=n)","preventionTips":["Treat start_line/end_line/total_lines as one atomic group: set all or none.","Build ReadResults through a single factory helper that fills in window fields consistently.","Unit-test your backend's read with all pagination fields present."],"tags":["validation","dataclass","backend-protocol"],"backgroundTag":"schema-validation-failed","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}