Added
- Parallel scanning: file detection now runs across a worker pool, so large libraries scan substantially faster. The default worker count is tuned to the memory available to the container so a constrained host won't be pushed into an out-of-memory kill; override with
HEALARR_SCANNER_WORKERS(1 to 32). - Configurable shutdown grace period for in-flight scans via
HEALARR_SCANNER_SHUTDOWN_TIMEOUT(default 30s). - Defense-in-depth HTTP security headers on all responses (
X-Content-Type-Options,X-Frame-Options,Referrer-Policy). - Corrupt releases are now blocklisted instead of re-grabbed. When a downloaded replacement is itself corrupt, Healarr deletes it, marks that specific release as failed in Sonarr/Radarr so the same release is not grabbed again, and lets the *arr fetch the next-best release. The original corruption still gets one grace re-search first (a single bad download is not always the release's fault); blocklisting kicks in only once a replacement comes back corrupt. Bounded by the path's retry limit, after which the item is flagged for attention.
- Stalled downloads are now blocklisted too. When a replacement never finishes downloading and times out, Healarr removes it from the download client and blocklists that release, so the re-search grabs a different release instead of waiting on the same dead one. Honors per-path auto-remediate / dry-run and is bounded by the retry limit.
- Modal dialogs now trap keyboard focus and expose proper ARIA roles, improving keyboard and screen-reader navigation.
Changed
- Faster database writes during scans: SQLite now uses
synchronous=NORMALunder WAL (still corruption-safe), and per-file scan-progress updates no longer hit the durable event store.
Fixed
- Files on a temporarily-unavailable mount or network share are no longer mistaken for corruption, preventing deletion of healthy files when a mount drops mid-scan. Mount/network errors are now classified by OS error code, so this also holds on non-English systems.
- Deletion now refuses to act on an ambiguous match. If the corrupt file can't be identified unambiguously in the *arr media (for example two files share a name), or the owning *arr instance is misconfigured, the remediation fails and is retried instead of risking deletion of a healthy file. An exact path match is preferred, with a unique-basename match as the only fallback.
- Per-path
auto_remediateand dry-run settings are now honored on retries and on startup recovery. Previously a corruption re-triggered after a restart could delete a file on a path configured as manual or dry-run; the remediator now re-reads the path's current settings before acting. - *A file that is not tracked in its arr no longer produces a fake "already deleted" success. Previously, on setups where the *arr sees media at a different mount path than Healarr, an untracked file could be reported as deleted, causing a search to be triggered while the corrupt file remained. The remediation now fails honestly and retries.
- Startup recovery of an interrupted deletion now uses a thorough (stream-level) health check instead of a quick header-only check, so a file with stream corruption is no longer prematurely marked resolved.
- Re-search now refuses to fall back to a whole-series search. When the specific episode cannot be identified, Healarr no longer issues a series-wide search that could re-download every missing episode; the attempt is surfaced instead.
- Active-corruption lookups no longer collide across sibling library roots. A path like
/media/TVpreviously matched/media/TV-Archive; lookups now require a path boundary. - A verified-corrupt replacement is now actually re-remediated. Previously, a retry after a failed verification only re-searched without removing the corrupt file; because the *arr will not replace a file that is still present, the item could stall. The retry now deletes the corrupt replacement before re-searching.
- A replacement that never finishes downloading no longer retries forever. Repeated download timeouts now count toward the path's retry limit, so an item that can never be satisfied escalates to a needs-attention state instead of re-searching indefinitely. (The retry count shown in the UI still reflects corruption failures only.)
- Live log and scan-progress updates no longer drop messages under load (WebSocket delivery rework).
- A paused scan could fail to resume if the resume signal raced with the scan loop; resume is now reliable.
Security
- The authentication token is no longer written to the browser console on WebSocket connect.
Full Changelog: v1.3.0...v1.3.1