fix(hunter): bound every tool result; show the provider's reason for an HTTP error

FUXA failed every hunt with HTTP 400, and neither run could say why. The client
discarded the response body. With the body shown, the reason was "This model's
maximum context length...": FUXA ships minified bundles whose single lines run to
megabytes, and grep_repo returned a matched line whole.

- grep_repo and find_refs quote a matched line (240 characters), never the whole
  line.
- Every tool result is capped at 16 x max_chars, with a truncation marker.
- OpenRouterChatClient errors carry the provider's response body (first 500
  characters). That would also have exposed the rejected model id in one call
  instead of eighteen days.

After the fix, FUXA candidates verify normally at $0.004-0.013 each. A test covers
the megabyte-line case.

verify_sinks.py: skip futures cancelled at the spend ceiling instead of reading
them (the run crashed with CancelledError).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-28 20:15:14 +02:00
co-authored by Claude Opus 5.5
parent 3ffb8b4c54
commit 8794884a64
5 changed files with 32 additions and 4 deletions
+4 -2
View File
@@ -32,6 +32,8 @@ def read_file(root: Path, path: str, *, max_chars: int) -> str:
return clamped.read_text(errors="ignore")[:max_chars]
# A matched line is quoted, not returned whole: a minified bundle's single line can be megabytes.
_LINE_CHARS = 240
_Lines = tuple[tuple[str, tuple[str, ...]], ...]
_SOURCE_MEMO: dict[tuple[str, int], tuple[_Lines, tuple[tuple[Path, int], ...]]] = {}
@@ -72,7 +74,7 @@ def grep_repo(root: Path, pattern: str, *, max_matches: int) -> list[dict[str, o
for relative, lines in _source_lines(root):
for line_no, line in enumerate(lines, start=1):
if compiled.search(line):
matches.append({"path": relative, "line": line_no, "text": line})
matches.append({"path": relative, "line": line_no, "text": line[:_LINE_CHARS]})
if len(matches) >= max_matches:
return matches
return matches
@@ -102,7 +104,7 @@ def find_refs(root: Path, symbol: str, mapping_index: object) -> list[dict[str,
for relative, lines in _source_lines(resolved_root):
for line_no, line in enumerate(lines, start=1):
if compiled.search(line):
refs.append({"path": relative, "name": symbol, "line": line_no, "text": line, "source": "text"})
refs.append({"path": relative, "name": symbol, "line": line_no, "text": line[:_LINE_CHARS], "source": "text"})
return refs