3.4 KiB
TECH.md — Fix read_files with out-of-bounds line ranges
Context
When an LLM requests a read_files tool call with line ranges entirely beyond EOF (e.g., lines 1891–2090 of a 1237-line file), the client returns ReadFilesResult::Success { files: [] } — the file silently vanishes from the result. This renders as an empty box with a green checkmark in the blocklist UI, and the server receives anyFilesSuccess: {} with zero files.
The root cause is in TextFileAccumulator::flush_range (crates/warp_files/src/text_file_reader.rs:131). The condition let should_emit = !self.buf.is_empty() || (final_flush && self.whole_file) only emits a segment on final flush for whole-file reads. For ranged reads where no lines fall within the requested range, buf stays empty and no segment is emitted. The caller (read_local_file_context in app/src/ai/blocklist/action_model/execute.rs:1029–1045) extends file_contexts from an empty segments iterator, then continues past the binary fallback — the file ends up in neither file_contexts nor missing_files.
Relevant files:
crates/warp_files/src/text_file_reader.rs:131—flush_rangewith theshould_emitguardapp/src/ai/blocklist/action_model/execute.rs:1029–1045—Segmentsbranch inread_local_file_contextapp/src/ai/blocklist/block/view_impl/output.rs:424–450— rendering logic forReadFilesResult::Success
Proposed changes
1. TextFileAccumulator::flush_range — always emit on final flush
Change the should_emit condition from !self.buf.is_empty() || (final_flush && self.whole_file) to !self.buf.is_empty() || final_flush. This makes the behavior consistent: every requested range always produces a TextFileSegment, even when the range is entirely past EOF. The emitted segment has content: "", the original requested line_range, and line_count set to the total file lines (populated by finalize).
This is a one-line change. The whole_file field remains used for trailing-newline preservation logic, so it is not removed.
2. Defensive rendering for empty file_contexts
In the ReadFilesResult::Success rendering arm, add a match guard if !file_contexts.is_empty() for the normal path. Add a second arm if file_contexts.is_empty() that renders an error-styled action box with a red X icon and "Failed to read files" message, then continues. This prevents an empty box regardless of upstream cause.
3. Tests
- Update existing
empty_file_with_ranges_produces_no_segment→ renamed toempty_file_with_ranges_produces_empty_segment, now expects 1 segment with empty content and the original range. - Add
range_past_eof_produces_empty_segment— 5-line file, range 10..15, expects 1 segment with empty content,line_count: 5. - Add
multiple_ranges_some_past_eof— 5-line file, ranges [1..3, 10..15], expects 2 segments: first with content, second empty.
Testing and validation
The fix is verified by the three unit tests above, all in crates/warp_files/src/text_file_reader_tests.rs. They cover:
- Empty file with ranges (previously zero segments, now one)
- Non-empty file with range entirely past EOF (the exact bug scenario)
- Mix of valid and out-of-bounds ranges in the same request
The defensive rendering change is not unit-tested (it requires the full UI framework context) but prevents the user-visible symptom for any future edge case that produces empty file_contexts.