{"record":{"id":"fc85501dd1be4416","repo":"langchain-ai/deepagents","slug":"readresult-next-offset-requires-start-line-and-end","errorCode":null,"errorMessage":"ReadResult.next_offset requires start_line and end_line to be set","messagePattern":"ReadResult\\.next_offset 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":258,"sourceCode":"        agree numerically: a window runs forward (`1 <= start_line <=\n        end_line`), the file is at least as long as the window\n        (`total_lines >= end_line`), and the resume point is the 0-indexed line\n        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","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/deepagents/deepagents/backends/protocol.py#L240-L276","documentation":"ReadResult.__post_init__ requires next_offset to be accompanied by a real line window: next_offset without start_line (and thus end_line, per the paired invariant) is rejected with ValueError. This prevents backends from advertising a continuation cursor for a result that has no window to continue from.","triggerScenarios":"Constructing ReadResult(content=..., next_offset=51) without start_line/end_line, typically from a backend implementing pagination incorrectly.","commonSituations":"Custom backend returning a truncated-content result with a continuation offset but forgetting to compute the line bounds that the offset was derived from.","solutions":["Set start_line and end_line whenever you set next_offset","Drop next_offset if the result is not a line-windowed read","Consult protocol.py's ReadResult invariants and align your backend's read implementation"],"exampleFix":"# before\nreturn ReadResult(content=chunk, next_offset=51)\n# after\nreturn ReadResult(content=chunk, start_line=1, end_line=50, next_offset=51)","handlingStrategy":"type-guard","validationCode":"if next_offset is not None:\n    assert start_line is not None and end_line is not None, \"next_offset requires a window\"","typeGuard":"def can_continue(r: ReadResult) -> bool:\n    return r.next_offset is not None and r.start_line is not None and r.end_line is not None","tryCatchPattern":"try:\n    return ReadResult(content=chunk, next_offset=n)\nexcept ValueError:\n    return ReadResult(content=chunk, start_line=1, end_line=len(chunk.splitlines()), next_offset=n)","preventionTips":["Derive next_offset from computed start_line/end_line, never set it alone","Centralize pagination construction in one backend helper with tests"],"tags":["python","dataclass-invariant","protocol","pagination"],"backgroundTag":"invariant-violation","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}