From ed67f5bd9f5f6767c31e1660509b2dc72a49d947 Mon Sep 17 00:00:00 2001 From: slxng1758 <148385226+slxng1758@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:16:21 -0400 Subject: [PATCH] fix(proxy/interceptors): validate truncation window length, not absolute end index _is_plausible_truncation_range compared end_line directly against the payload's line count, which only holds when a read starts at line 1. For an offset read (e.g. "showing lines 100-148 of 300"), the payload only contains that window's ~49 lines, so the check silently rejected a genuinely truncated, correctly-formatted banner. Validate the inclusive window length (end_line - start_line + 1) instead, which is invariant to where the window starts. Also collapses test_astgrep_flags_truncated_read onto the _read_result_messages() helper introduced later in the same file, removing a leftover duplicate. Co-Authored-By: Claude Sonnet 5 --- headroom/proxy/interceptors/astgrep.py | 3 +- tests/test_tool_result_interceptors.py | 49 +++++++++++++++++--------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/headroom/proxy/interceptors/astgrep.py b/headroom/proxy/interceptors/astgrep.py index 9f77619ad..e783b0883 100644 --- a/headroom/proxy/interceptors/astgrep.py +++ b/headroom/proxy/interceptors/astgrep.py @@ -111,7 +111,8 @@ def _is_plausible_truncation_range( # end_line == total_lines means the whole file was shown, not truncated. if start_line < 1 or end_line < start_line or end_line >= total_lines: return False - return end_line <= source_line_count + window_length = end_line - start_line + 1 + return window_length <= source_line_count def _detect_truncation(source: str) -> tuple[int, int] | None: diff --git a/tests/test_tool_result_interceptors.py b/tests/test_tool_result_interceptors.py index 76b54f5ca..e2e3ff44b 100644 --- a/tests/test_tool_result_interceptors.py +++ b/tests/test_tool_result_interceptors.py @@ -221,23 +221,7 @@ def test_astgrep_flags_truncated_read(tokenizer): "showing lines 1-42 of 90 total (26031 tokens, cap 25000). " "Call Read with offset=43 to see more.]\n" ) - messages = [ - { - "role": "assistant", - "content": [ - { - "type": "tool_use", - "id": "abc", - "name": "Read", - "input": {"file_path": "/repo/payments.py"}, - } - ], - }, - { - "role": "user", - "content": [{"type": "tool_result", "tool_use_id": "abc", "content": truncated_source}], - }, - ] + messages = _read_result_messages(truncated_source) result = apply_to_messages(messages, tokenizer) assert len(result.spans) == 1 new_content = result.messages[1]["content"][0]["content"] @@ -343,6 +327,37 @@ def test_astgrep_accepts_truncation_at_start_equals_end(tokenizer): assert "showing through line 42 of 90 total" in new_content +def test_astgrep_flags_truncated_read_offset_window(tokenizer): + """A legitimate offset read (not starting at line 1) must still be + recognized -- window length, not the absolute end index, is what's + checked against the visible payload.""" + truncated_source = ( + _PY_FIXTURE + "\n\n[Truncated: PARTIAL view — /repo/payments.py: " + "showing lines 100-148 of 300 total (26031 tokens, cap 25000). " + "Call Read with offset=149 to see more.]\n" + ) + messages = _read_result_messages(truncated_source) + result = apply_to_messages(messages, tokenizer) + new_content = result.messages[1]["content"][0]["content"] + assert "truncated upstream" in new_content + assert "showing through line 148 of 300 total" in new_content + + +def test_astgrep_ignores_offset_window_exceeding_visible_payload(tokenizer): + """An offset window is still checked for plausibility -- a claimed + window far longer than what's actually visible must not trigger.""" + truncated_source = ( + _PY_FIXTURE + "\n\n[Truncated: PARTIAL view — /repo/payments.py: " + "showing lines 100-5148 of 9999 total (26031 tokens, cap 25000). " + "Call Read with offset=5149 to see more.]\n" + ) + messages = _read_result_messages(truncated_source) + result = apply_to_messages(messages, tokenizer) + new_content = result.messages[1]["content"][0]["content"] + assert "outlined by ast-grep" in new_content + assert "truncated upstream" not in new_content + + def test_astgrep_skips_small_files(tokenizer): small = "def foo(): return 1\n" messages = [