fix(read): correct off-by-one for negative offset with trailing newline

The Read tool documents that a negative offset reads from the end (-N = last N lines). Content is split on newline, so a file ending in a newline produces a trailing empty element and total_file_lines is one too high. Every negative offset was therefore off by one: offset=-1 returned the empty string after the final newline instead of the last line, and -N returned N-1 real lines.

Exclude the trailing empty element when computing the start line for negative offsets. Adds regression tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
xiaweiwei67-stack
2026-07-07 16:53:35 +08:00
parent d531e14fbf
commit fcc520df47
2 changed files with 67 additions and 2 deletions

View File

@@ -297,8 +297,15 @@ class Read(BaseTool):
if offset is not None:
if offset < 0:
# Negative offset: read from end
# -20 means "last 20 lines" → start from (total - 20)
start_line = max(0, total_file_lines + offset)
# -20 means "last 20 lines" → start from (total - 20).
# A file ending in "\n" produces a trailing empty element
# from split('\n'); exclude it so offset=-1 returns the
# real last line instead of the empty string after the
# final newline (and -N returns N real lines).
effective_lines = total_file_lines
if all_lines and all_lines[-1] == '':
effective_lines -= 1
start_line = max(0, effective_lines + offset)
else:
# Positive offset: read from start (1-indexed)
start_line = max(0, offset - 1) # Convert to 0-indexed