fix(scan): non-fatal batch errors + non-JSON response diagnostics (#316)#317
Merged
Conversation
- Add early non-JSON response guard in parse_scan_response: detect HTML error pages, rate-limit bodies, empty responses, and prose wrappers before attempting strict parse. Surface raw response prefix (first 512 bytes, whitespace-collapsed) in the error so users can diagnose truncation vs provider error vs prose. - Make per-batch parse failures non-fatal by default: skip the failing batch with a stderr warning and warn-level log (with file list), continue with remaining batches. --no-continue-on-batch-error restores old abort behavior. - Add --batch-files <N> flag (default 20) to cap files per LLM batch — lower it to work around provider token limits on large scans. - Truncated-JSON repair path now also includes raw response prefix. - Add 14 unit tests covering looks_like_json_array, preview_raw, and parse_scan_response non-JSON rejection. - Bump version 0.6.0 → 0.6.1 (patch: bug fix, no API breaking changes).
This was referenced Jun 17, 2026
This was referenced Jun 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #316 —
cora scanaborts withfailed to parse LLM JSON response: expected value at line 1 column 1on large batches.Root cause
When a scan batch is large, the LLM/provider returns a response that isn't valid JSON (truncated, wrapped in prose, or an error/rate-limit body).
parse_scan_responsethen hard-failed the entire scan with a minimal error, no per-file results, and no way to see what the provider actually returned.Changes
1. Early non-JSON guard (
parse_scan_response)Add
looks_like_json_array()that checks whether the trimmed response (after optional ```json fences) starts with[or `{`. If not, fail fast with `non_json_error_message()` which includes:preview_raw()).So users can immediately tell whether it's a provider error page, rate-limit message, or prose wrapper.
2. Per-batch parse failures are now non-fatal by default
In
execute_scan, each batch call is wrapped inmatch. OnErr:warnlevel with batch index + file list (shows even without--verbose),skipped_batchesand continue with the next batch,--no-continue-on-batch-errorrestores the old abort-on-failure behavior.3. New
--batch-files <N>flag (default: 20)Lets users lower the max files per batch to work around provider token limits / rate-limits on large scans.
4. Better diagnostics
Both the truncated-JSON repair path and the general parse-error path now append the raw response prefix.
Tests
src/engine/llm.rscoveringlooks_like_json_array,preview_raw, andparse_scan_responsenon-JSON rejection (HTML page, empty body, prose).cargo clippy -- -D warningsclean,cargo fmtapplied.Versioning
Patch bump
0.6.0→0.6.1per the existing release flow (GitFlow: merge tomainvia PR, then tagv0.6.1). No API breaking changes.Checklist
cargo testpassescargo clippy -- -D warningscleancargo fmtapplied## [0.6.1]Cargo.toml+Cargo.lock[0.6.0]entry)