langchain-ai/deepagents · error · ValueError
ReadResult.total_lines requires start_line and end_line to b
Error message
ReadResult.total_lines requires start_line and end_line to be set
What it means
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.
Source
Thrown at libs/deepagents/deepagents/backends/protocol.py:261
immediately after the last one shown (`next_offset == end_line`, since
`end_line` is 1-indexed). Fail loudly here to keep a backend from
emitting a `next_offset` that would silently skip unshown source lines
once it reaches the middleware.
"""
if (self.start_line is None) != (self.end_line is None):
msg = "ReadResult.start_line and end_line must be set together or both left unset"
raise ValueError(msg)
if self.no_lines_requested and (
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
):
msg = "ReadResult.no_lines_requested describes an uninspected window; it cannot be combined with error or pagination fields"
raise ValueError(msg)
if self.next_offset is not None and self.start_line is None:
msg = "ReadResult.next_offset requires start_line and end_line to be set"
raise ValueError(msg)
if self.total_lines is not None and self.start_line is None:
msg = "ReadResult.total_lines requires start_line and end_line to be set"
raise ValueError(msg)
# Numeric consistency of a present window. `start_line`/`end_line` are
# bound together above, so testing `start_line` covers both.
if self.start_line is not None and self.end_line is not None:
if self.start_line < 1 or self.end_line < self.start_line:
msg = f"ReadResult window must satisfy 1 <= start_line <= end_line, got start_line={self.start_line}, end_line={self.end_line}"
raise ValueError(msg)
if self.total_lines is not None and self.total_lines < self.end_line:
msg = f"ReadResult.total_lines ({self.total_lines}) cannot be less than end_line ({self.end_line})"
raise ValueError(msg)
if self.next_offset is not None and self.next_offset != self.end_line:
msg = f"ReadResult.next_offset ({self.next_offset}) must equal end_line ({self.end_line}), the 0-indexed line after the last shown"
raise ValueError(msg)
@dataclass
class WriteResult:
"""Result from backend `write` operations.View on GitHub (pinned to a1af029e6e)
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.
Example fix
// before
return ReadResult(content=text, total_lines=len(all_lines))
// after
return ReadResult(
content=text,
start_line=1,
end_line=len(lines_returned),
total_lines=len(all_lines),
) Defensive patterns
Strategy: validation
Validate before calling
def is_valid_read_result_fields(**fields):
if fields.get('total_lines') is not None:
return fields.get('start_line') is not None and fields.get('end_line') is not None
return True
# call before constructing: is_valid_read_result_fields(total_lines=120, start_line=None, end_line=None) -> False Type guard
def has_consistent_window(r) -> bool:
return not (r.total_lines is not None and (r.start_line is None or r.end_line is None)) Try / catch
try:
result = ReadResult(content=text, total_lines=n)
except ValueError as e:
logger.warning('invalid ReadResult: %s', e)
result = ReadResult(content=text, start_line=1, end_line=len(text.splitlines()), total_lines=n) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- shell_allow_list must be None or non-empty
- goal application requires a non-empty objective and rubric
- RewarmEstimate costs must be finite, got cold={self.cold_cos
- RewarmEstimate costs must be non-negative, got cold={self.co
- RewarmEstimate incremental cost {self.incremental_cost_usd!r
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/7db233fb1219912f.
Report an issue: GitHub.