Skip to content

fix(worker): One storage config with a missing root stops the whole Worker #282

Closed

Description

@bitbiter-dev

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.ProduceAsync rethrows a failure to list the root (only subfolders are logged and skipped). RunAsync_RootDirectoryDoesNotExist_ThrowsDirectoryNotFoundExceptionAsync pins this.
  • Worker.CrawlAllAsync loops over the configs without a catch, so the exception leaves RecurringWork.RunAsync and faults the BackgroundService.
  • No HostOptions.BackgroundServiceExceptionBehavior is set, so the .NET default StopHost applies and the whole host stops, TokenCleanupService included.
  • The crawl runs as soon as the Worker starts, and docker-compose.yml restarts 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.
  • FileIngestionPipeline keeps throwing for a missing root. Only the per-config loop catches it.
  • scripts/mutation-changed.sh at 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingready-for-agentFully specified, ready for an AFK agentv1.0Required for the v1.0 releaseworker

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions