Skip to content

Keep scanning when one file or one model response fails - #155

Merged
matt-edmondson merged 3 commits into
mainfrom
fix/150-scan-survives-bad-items
Sep 26, 2026
Merged

matt-edmondson merged 3 commits into
mainfrom
fix/150-scan-survives-bad-items

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #150

Summary

In both parallel phases of Scan, one failed item ended the whole run with an unhandled AggregateException.

  • Hashing: ImageHasher.HashFiles catches IOException and UnauthorizedAccessException for each file, prints a warning, and leaves that file out of the results. This covers dangling symlinks and locked or permission-denied files.
  • Describing: the describe loop moves out of Scan.Run into internal static Scan.DescribeImages(..., Action<ImageDescription> store). It now catches JsonException (non-JSON response), OperationCanceledException (which covers the TaskCanceledException from the 10-minute HttpClient.Timeout), IOException and UnauthorizedAccessException, as well as HttpRequestException. Each failure is logged and collected, and the remaining images carry on. Run passes a store callback that keeps the original lock-then-Save() behaviour.
  • Scan ends with a "Failed to describe N image(s):" summary listing each failed path and its reason.

The optional per-request timeout and retry from the issue are not included; the timeout is now contained rather than fatal.

Test

  • HashFilesSkipsFilesThatCannotBeRead hashes an existing file together with a missing one. The existing file must be hashed and the missing one dropped.
  • DescribeImagesContinuesPastPerImageFailures runs DescribeImages against a local HttpListener stub of /api/generate. The stub returns an HTML page for bad.jpg, and a third file is deleted before the run. good.jpg must still be described and stored, and two failures must come back, including the JsonException for bad.jpg.
  • With both catches reverted to their originals, both tests fail with the AggregateExceptions the issue reports (FileNotFoundException and '<' is an invalid start of a value). With the fix, the full suite passes 39/39, stable over 5 runs.

This branch merges cleanly with #152, #153 and #154.

🤖 Generated with Claude Code

https://claude.ai/code/session_0113g7mUxeizhTrH6na279Ez


Generated by Claude Code

HashFiles let an IOException from one unreadable or dangling file escape
Parallel.ForEach, and the describe loop caught only HttpRequestException, so a
non-JSON response, a request timeout or a file vanishing after hashing ended the
whole run with an unhandled AggregateException.

HashFiles now warns about and leaves out a file it cannot read. The describe
phase moves into Scan.DescribeImages, which logs and collects per-image
failures, including JsonException, the timeout's OperationCanceledException and
IOException, and carries on with the other images. Scan then prints a summary
of what failed.

Fixes #150

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113g7mUxeizhTrH6na279Ez
Runs the Scan verb against the fake Ollama endpoint with an image the server
answers with HTML, and checks that the run completes and reports the failure.
This covers the new code in Scan.Run that DescribeImages' own test cannot reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113g7mUxeizhTrH6na279Ez
SonarCloud flagged Scan.Run's cognitive complexity (16, limit 15) once it
printed the summary inline. Also switch the new tests to Assert.HasCount and
Assert.Contains, as the MSTest analyzers suggest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113g7mUxeizhTrH6na279Ez
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 9ea34f3 into main Sep 26, 2026
12 checks passed
@matt-edmondson
matt-edmondson deleted the fix/150-scan-survives-bad-items branch September 26, 2026 09:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A single unreadable file or bad/timed-out model response crashes the whole scan with an unhandled AggregateException

2 participants