fix(read_file): detect metadata-only responses by the request flag - #987
fix(read_file): detect metadata-only responses by the request flag#987stag7824 wants to merge 3 commits into
Conversation
The formatter decided a response was metadata-only with
result?.startsWith('File:')
but the metadata branch emits `File Information for "..."`, so the test never
matched - the fifth character is a space, not a colon.
The same condition additionally required no line range and a file longer than
FILE_READ_PREVIEW_THRESHOLD_LINES. Those describe the truncated-preview
response, which is already detected separately on the next line; the metadata
branch returns before any line range or size is considered, so a
`metadata_only` read on a small file could not have matched either.
isMetadataOnly was therefore always false, and a `metadata_only: true` read
rendered as an ordinary content read: no "(metadata only)" marker, the
content-read layout instead of the metadata layout, and a token count measured
from the metadata block rather than from the full file.
Fixes Nano-Collective#970
will-lamerton
left a comment
There was a problem hiding this comment.
Diagnosis is correct and I reproduced it: on main a metadata_only: true read rendered Lines: 1 - 4 and Tokens: ~89 (tokens measured from the metadata block), and on this branch it correctly renders (metadata only) / Total lines: 4 with no Tokens: row. Types, lint, format and the 42 existing read-file.spec.tsx tests all pass.
Three things before merge:
1. Needs a regression test. This bug shipped precisely because no formatter test ever passes metadata_only: true - the three existing ReadFileFormatter tests cover path, truncated preview and error results only, and the metadata_only tests exercise the handler rather than the formatter. Nothing in the suite would catch this coming back. A test asserting (metadata only) renders and Tokens: does not would close it.
2. Directories and unreadable files still render the wrong layout. isMetadataOnly is computed inside the try, after getCachedFileContent(), which throws EISDIR for a directory. So a metadata_only read of a directory still falls into the catch and renders the content layout:
⚒ read_file
Path: .../some-dir
Lines: 1 - 0
Tokens: ~0
The tool explicitly supports directories (if (type === 'directory') at line 94, with a handler test for it), so this is the same class of bug left half-fixed. Hoisting the flag above the try and into the initial fileInfo handles both cases.
3. === true disagrees with the executor. executeReadFile gates on truthy if (args.metadata_only) while the formatter now requires === true. The XML fallback path can produce non-boolean truthy values: xml-parser.ts:161 does JSON.parse() and falls back to the raw string, so <metadata_only>True</metadata_only> arrives as a string. I confirmed that passing 'true' reproduces the exact bug being fixed here. Boolean(args.metadata_only) keeps the two predicates identical by construction and fixes this alongside point 2.
Two smaller notes, take or leave:
- With
isMetadataOnlynow actually true,tokens = calculateTokens(content)runs a full-file tokenization whose result is never rendered, since theTokens:row is hidden for metadata reads. Worth skipping. - The 6-line comment is mostly archaeology about why the old code was wrong. Comments elsewhere in this file explain intent rather than history, and the changeset already tells that story. One line on what the flag means would fit better.
Three changes from will-lamerton's review of Nano-Collective#987: - isMetadataOnly is now set from args.metadata_only before the file read, not derived after it succeeds. executeReadFile's own metadata branch never calls getCachedFileContent for a directory or symlink (only for type === 'file'), so the formatter's identical call threw the same way, landing in the catch and losing the flag - a metadata_only read of a directory still rendered the content layout. - isMetadataOnly now uses Boolean(args.metadata_only) instead of `=== true`, matching executeReadFile's own truthy check. The XML tool-call fallback can hand this through as the string 'true', which is truthy but not strictly equal to true, and would otherwise reproduce the exact bug this PR fixes. - Skip calculateTokens() for a metadata-only read; the Tokens: row is hidden for that layout, so the value was computed and discarded. Added three regression tests: a metadata_only read on a regular file, one on a directory, and one with metadata_only passed as a truthy string. Trimmed the inline comment per review - the changeset carries the "why" now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AycxQUU3D4C9D45UQfDEhb
|
Thanks for the thorough review and for reproducing it independently — all three addressed in 17ea57f:
Also took the two smaller suggestions: skipped
|
…ata-only-detection
Fixes #970
The bug
readFileFormatterdecided whether a response was metadata-only like this:The metadata branch emits (
read-file.tsx:48):"File Information for ..."does not start with"File:"— the fifth character is a space, not a colon. So the prefix test never matched andisMetadataOnlywas always false.The other half of it
The three remaining conditions describe the truncated-preview response, not this one.
read_filereturns one of three shapes:isMetadataOnly← brokenresult.includes('[Truncated at line ')✅The metadata branch is guarded by
if (args.metadata_only)at line 34 and returns before any line range or file size is considered. So requiring!args.start_line && !args.end_line && totalLines > 1500could never be right for it — ametadata_onlyread on a 120-line file fails the threshold even if the prefix had matched.Impact
With
isMetadataOnlystuck false, ametadata_only: trueread renders as an ordinary content read:(metadata only)marker never appears (line 254)Tokens:row renders even though it is meant to be hidden for metadata reads (line 281)result— the metadata block itself — rather than from the full file (lines 349–355), so the number describes the wrong thingThe change
args.metadata_onlyis the flag that selects this response shape at line 34, so it is exactly the right signal.FILE_READ_PREVIEW_THRESHOLD_LINESis still used at line 123, so the import stays live.Verification
Against the exact string the metadata branch emits:
The truncated-preview and plain-content paths are untouched —
isTruncatedon the following line is unchanged, and neither path setsmetadata_only.A changeset is included (
@nanocollective/nanocoder, patch).