Skip to content

Python: [BREAKING] Issue 7571 file access read lines - #7669

Open
Anton Sokolovskyi (antsok) wants to merge 5 commits into
microsoft:mainfrom
antsok:issue-7571-file-access-read-lines
Open

Python: [BREAKING] Issue 7571 file access read lines#7669
Anton Sokolovskyi (antsok) wants to merge 5 commits into
microsoft:mainfrom
antsok:issue-7571-file-access-read-lines

Conversation

@antsok

@antsok Anton Sokolovskyi (antsok) commented Aug 14, 2026

Copy link
Copy Markdown

Motivation & Context

The harness file tools are line-precise when editing: file_access_replace_lines takes 1-based line numbers and file_access_grep reports 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_grep reported each match with its terminator stripped, while file_access_replace_lines takes new_line literally. An agent that grepped a CRLF file and edited by line number had
no 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

  • What are the major changes?

file_access_read_lines(file_name, start_line, end_line=None) reads a 1-based inclusive range. Omit end_line to read to the end of the file; an end_line past the last line clamps to the last line, while an out-of-range start_line is 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 into file_access_replace_lines without 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_grep now 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_INSTRUCTIONS gains 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.

  • What is the impact of these changes?

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 follows disable_readonly_tool_approval, and it stays advertised under disable_write_tool. Consumers get one more tool definition per request; anyone using read_only_tools_auto_approval_rule picks it up automatically, while anyone with a hand-rolled rule listing the read-only tools by name will see it prompt for approval
until they add it.

The breaking part is grep: matching_lines[].line now carries its terminator, and FileSearchMatch.line changes meaning with it. file_memory_grep inherits the same behaviour, since it shares the store search. The instruction changes alter injected
prompt text for consumers on the default; anyone passing their own instructions= is unaffected. There is no change to AgentFileStore and no change to any other tool's signature or behaviour.

  • What do you want reviewers to focus on?

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

  • The code builds cleanly without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue, and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

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>
Copilot AI balanced review requested due to automatic review settings August 14, 2026 18:04
@agent-framework-automation agent-framework-automation Bot added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Aug 14, 2026
@github-actions github-actions Bot changed the title Issue 7571 file access read lines (Python) Python: Issue 7571 file access read lines (Python) Aug 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds line-range reads to Python harness file tools while preserving line-number and terminator semantics.

Changes:

  • Adds file_access_read_lines with 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.

Comment thread python/packages/core/agent_framework/_harness/_file_access.py
Comment thread python/packages/core/agent_framework/_harness/_file_access.py
@antsok Anton Sokolovskyi (antsok) changed the title Python: Issue 7571 file access read lines (Python) Python: [BREAKING] Issue 7571 file access read lines Aug 14, 2026
@agent-framework-automation agent-framework-automation Bot added the breaking change Usage: [PRs], Target: all PRs that introduce changes that are not backward compatible label Aug 14, 2026
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>
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 as match$ therefore start matching CRLF files, and patterns targeting \r stop matching, despite the PR description saying matching remains as before. Either remove only \n to 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Usage: [PRs], Target: all PRs that introduce changes that are not backward compatible documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants