Repository navigation
fix(worker): One storage config with a missing root stops the whole Worker #282
Description
From the 2026-10-18 architecture review (#275) of arch-review/2026-10-16..917f973.
Problem
One storage config whose root is missing or unreadable stops the whole Worker, and keeps it stopped.
FileIngestionPipeline.ProduceAsyncrethrows a failure to list the root (only subfolders are logged and skipped).RunAsync_RootDirectoryDoesNotExist_ThrowsDirectoryNotFoundExceptionAsyncpins this.Worker.CrawlAllAsyncloops over the configs without a catch, so the exception leavesRecurringWork.RunAsyncand faults theBackgroundService.- No
HostOptions.BackgroundServiceExceptionBehavioris set, so the .NET defaultStopHostapplies and the whole host stops,TokenCleanupServiceincluded. - The crawl runs as soon as the Worker starts, and
docker-compose.ymlrestarts it (restart: unless-stopped). An unmounted NAS share therefore causes a crash loop. - In all-user mode, the configs after the failing one are never crawled, so one user's missing share blocks every other user.
CLAUDE.md says a missing root "still fails the crawl". That means that config's crawl, not the process. The API already reports a missing mount as Degraded instead of failing (/api/v1/healthz).
The work queue removes this loop (#281: one Crawl item per config, and a handler exception fails only that item). This issue is the stop-gap until then, because #281 is queued behind #278.
What to build
In Worker.CrawlAllAsync, a failed crawl of one config is logged with its root path and user, and the loop goes on to the next config. Shutdown cancellation still propagates (the IngestionShutdown.IsInProgress rule the consumers use). The crawl timer then waits for the next interval as usual.
Acceptance
- A config whose root does not exist is logged as an error naming its root path, and the next config is still crawled in the same pass.
- The Worker keeps running, and the next interval crawls the failing config again.
- A shutdown during a crawl still stops the loop and is not logged as a crawl failure.
-
FileIngestionPipelinekeeps throwing for a missing root. Only the per-config loop catches it. -
scripts/mutation-changed.shat 100%.
First caller
Worker.CrawlAllAsync, the existing crawl loop, on every crawl.
Design
Design: not needed because it adds a catch-and-log to an existing loop, with no new type, contract or schema change, and #281 replaces the loop.
Activity
fix(worker): One storage config with a missing root no longer stops t…