Python: [BREAKING] Issue 7571 file access read lines - #7669
Open
Anton Sokolovskyi (antsok) wants to merge 5 commits into
Open
Python: [BREAKING] Issue 7571 file access read lines#7669Anton Sokolovskyi (antsok) wants to merge 5 commits into
Anton Sokolovskyi (antsok) wants to merge 5 commits into
Conversation
The harness file tools are line-precise when editing but all-or-nothing when reading, so there is no way to see the lines around a file_access_grep match before editing them. Models either re-read the whole file, which is expensive and gets truncated on exactly the large files where partial reads matter, or skip the read and guess at the line. Add file_access_read_lines(file_name, start_line, end_line=None): a 1-based inclusive range read returning numbered text, headed by the total line count and the file's line-ending style. Omit end_line to read to the end of the file; a value past the last line clamps to it, while an out-of-range start_line is reported. Line numbers come from the same _split_lines_keepends split that backs file_access_grep and file_access_replace_lines, so a number reported by grep addresses the same line in all three tools, including the trailing empty line of a newline-terminated file. The header names the line endings because grep strips terminators while replace_lines takes them literally. The tool is read-only: it joins _READ_ONLY_TOOL_NAMES so both static auto-approval rules cover it, it follows disable_readonly_tool_approval, and it stays advertised under disable_write_tools. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review feedback on microsoft#7571. The header largely echoed the tool call, and the gutter already reveals the end of the file: an end_line past the end comes back with a lower last number, and omitting end_line reads to EOF by definition. The total line count added nothing the caller could not derive. Keeping each line's terminator instead of stripping it removes the need for a line-ending indicator altogether. Every row carries its own terminator, so a mixed-ending file needs no detection, and the text after the gutter can be reused as a file_access_replace_lines new_line without dropping a \r\n. The terminator doubles as the row separator. Drops _strip_line_terminator and _line_ending_style, and returns _slice_lines to a plain list now that the total is unused. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
file_access_grep reported each hit with its terminator stripped, while file_access_replace_lines takes new_line literally. A model that grepped a CRLF file and then edited by line number had no way to know it should write \r\n, so the edit silently converted the line. That is the same gap file_access_read_lines closes on the read path, and it stays open for anyone who edits straight off a grep hit. _search_file_content now splits with _split_lines_keepends and reports the line verbatim. The pattern is still matched against the line without its trailing \n, so ^ and $ anchor per line as before, and snippet offsets and line numbers are unchanged. file_memory_grep gets the same behaviour, since it shares the store search. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Anton Sokolovskyi (antsok)
deployed
to
github-app-auth
August 14, 2026 18:04 — with
GitHub Actions
Active
Anton Sokolovskyi (antsok)
deployed
to
github-app-auth
August 14, 2026 18:04 — with
GitHub Actions
Active
Anton Sokolovskyi (antsok)
deployed
to
github-app-auth
August 14, 2026 18:04 — with
GitHub Actions
Active
Anton Sokolovskyi (antsok)
deployed
to
github-app-auth
August 14, 2026 18:05 — with
GitHub Actions
Active
Copilot started reviewing on behalf of
Anton Sokolovskyi (antsok)
August 14, 2026 18:05
View session
Contributor
There was a problem hiding this comment.
Pull request overview
Adds line-range reads to Python harness file tools while preserving line-number and terminator semantics.
Changes:
- Adds
file_access_read_lineswith approval integration. - Changes grep matches to retain line terminators.
- Adds tests and documentation.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
python/samples/02-agents/harness/README.md |
Documents the new auto-approved tool name. |
python/samples/02-agents/harness/build_your_own_claw/README.md |
Updates security guidance. |
python/packages/core/tests/core/test_harness_file_access.py |
Tests range reads, terminators, and approvals. |
python/packages/core/AGENTS.md |
Documents new behavior and API. |
python/packages/core/agent_framework/_harness/_file_access.py |
Implements range reads and grep changes. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Anton Sokolovskyi (antsok)
deployed
to
github-app-auth
August 14, 2026 18:09 — with
GitHub Actions
Active
Anton Sokolovskyi (antsok)
deployed
to
github-app-auth
August 14, 2026 18:10 — with
GitHub Actions
Active
Anton Sokolovskyi (antsok)
deployed
to
github-app-auth
August 14, 2026 18:11 — with
GitHub Actions
Active
The new tool joined _READ_ONLY_TOOL_NAMES but several public docstrings still enumerated the read-only set as read/ls/grep. Two of them were actively misleading rather than merely stale: the disable_write_tools docs in both FileAccessProvider and create_harness_agent said only read/ls/grep stay advertised, implying file_access_read_lines is hidden when it is not. The security warning on read_only_tools_auto_approval_rule matters most. It lists the names the rule auto-approves so callers can avoid collisions, so leaving one out understates which names are reserved. Covers _file_access.py (disable_write_tools, disable_readonly_tool_approval, the rule description and its warning) and _agent.py (file_access_disable_write_tools, file_access_disable_readonly_tool_approval). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Anton Sokolovskyi (antsok)
deployed
to
github-app-auth
August 14, 2026 18:25 — with
GitHub Actions
Active
Anton Sokolovskyi (antsok)
deployed
to
github-app-auth
August 14, 2026 20:17 — with
GitHub Actions
Active
…s_grep Ports the fix for the same defect found by review on the .NET side (microsoft#7671). _search_file_content removed only the trailing "\n" before matching, so on a CRLF file the pattern was applied to text such as "beta match\r" and an end-anchored pattern like "match$" failed even though the line's text is exactly "beta match". The terminator is not part of the line's text, so it is stripped in full now. The per-line offset had to move with it: it advanced by len(scanned) + 1, which was only correct while scanned still carried the "\r". It now advances by len(line), whose terminator is already included, keeping the snippet anchored at the match. Also drops a stale claim in _split_lines_keepends' docstring, which still said it reproduced _search_file_content's content.split("\n") — that dependency now runs the other way round. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Anton Sokolovskyi (antsok)
deployed
to
github-app-auth
August 14, 2026 20:53 — with
GitHub Actions
Active
Copilot started reviewing on behalf of
Anton Sokolovskyi (antsok)
August 14, 2026 20:56
View session
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (2)
python/packages/core/agent_framework/_harness/_file_access.py:524
- This also changes which grep results match: previously
content.split("\n")searched a CRLF line as"...\r", whereas this removes both terminator characters. Patterns such asmatch$therefore start matching CRLF files, and patterns targeting\rstop matching, despite the PR description saying matching remains as before. Either remove only\nto preserve the old behavior, or explicitly document this additional breaking change and its impact.
scanned = line.removesuffix("\n").removesuffix("\r")
python/packages/core/agent_framework/_harness/_file_access.py:243
- The element-count phrase was dropped, leaving “means the result has trailing … yields” grammatically incomplete. Restore the count clause so the split contract is readable.
line in the others and stays in range. Splitting solely on ``\n`` (a trailing
``\r`` stays attached to the line) means the result has
trailing ``\n`` yields a final empty (editable) line, and empty content yields a
single empty line. ``"".join(...)`` reproduces ``content`` verbatim.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation & Context
The harness file tools are line-precise when editing:
file_access_replace_linestakes 1-based line numbers andfile_access_grepreports them — but all-or-nothing when reading. There is no way to see the lines around a grep match before editing them, so a model either re-reads the whole file (expensive, and truncated by host result caps on exactly the large files where a partial read matters) or skips the read and guesses at the line, which is the wrong-line edit the line-precise editor exists to prevent.A second, related gap surfaced while implementing this.
file_access_grepreported each match with its terminator stripped, whilefile_access_replace_linestakesnew_lineliterally. An agent that grepped a CRLF file and edited by line number hadno way to know it should write
\r\n, so the edit silently converted that line - and the only way to detect the file's line-ending style was a whole-file read, the very fallback this change removes.Description & Review Guide
file_access_read_lines(file_name, start_line, end_line=None)reads a 1-based inclusive range. Omitend_lineto read to the end of the file; anend_linepast the last line clamps to the last line, while an out-of-rangestart_lineis reported. Rows are<line_number>\t<line>, and everything after the tab is verbatim, including the line's own terminator — which therefore doubles as the row separator - so a row's text feeds straight back intofile_access_replace_lineswithout losing a\r\n.Line numbering comes from
_split_lines_keepends, the same split used by grep and replace_lines, so a number maps to the same line in all three tools, including the trailing empty line of a newline-terminated file.file_access_grepnow reports matches verbatim on the same contract. The pattern is still matched against the line without its trailing\n, so^and$anchor per line as before, and snippet offsets and line numbers are unchanged.DEFAULT_FILE_ACCESS_INSTRUCTIONSgains a clause teaching the grep → read the range edit loop, which is what makes models reach for the new tool rather than defaulting to whole-file reads.The new tool is read-only. It joins
_READ_ONLY_TOOL_NAMES, so both static auto-approval rules cover it with no new approval logic; it followsdisable_readonly_tool_approval, and it stays advertised underdisable_write_tool. Consumers get one more tool definition per request; anyone usingread_only_tools_auto_approval_rulepicks it up automatically, while anyone with a hand-rolled rule listing the read-only tools by name will see it prompt for approvaluntil they add it.
The breaking part is grep:
matching_lines[].linenow carries its terminator, andFileSearchMatch.linechanges meaning with it.file_memory_grepinherits the same behaviour, since it shares the store search. The instruction changes alter injectedprompt text for consumers on the default; anyone passing their own
instructions=is unaffected. There is no change toAgentFileStoreand no change to any other tool's signature or behaviour.Whether the grep alignment belongs in this PR or should be split out - it is a separate commit so it can be dropped. And the verbatim-terminator contract itself, since that is what the tool's usefulness rests on.
Related Issue
#7571
Deliberately linked without a closing keyword: this covers the Python half only, and the .NET port follows in a separate PR. Please leave the issue open until that lands. Will change to Closes in the last one.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.