Summary
On hosts with a kernel older than 4.11, STATX_BTIME is unavailable, so directory identity capture fails. The codebase already models this outcome, DirectoryObjectIdentityFailureKind.IdentityUnsupported, with a user-facing message, but the scan authorization path doesn't route through it. The PlatformNotSupportedException escapes into a BackgroundService, and under .NET's default StopHost behavior the entire host shuts down. The user sees an API 502 and a container restart loop instead of the intended "this storage location does not expose the directory identity Listenarr requires" message.
Environment
- Listenarr
ghcr.io/listenarrs/listenarr:canary, version 1.3.0
- Synology DSM, kernel
4.4.302+; btrfs on /volume1/Music/Audiobooks bind-mounted to /audiobooks
- Source references below are against canary
f27c798
Mechanism
PinnedDirectoryCreation.NativeOperations.cs:223-247 requires birth time:
const uint requiredMask = 0x00000100 | 0x00000800; // STATX_INO | STATX_BTIME
if (Statx(handle.DangerousGetHandle().ToInt32(), string.Empty, 0x1000, requiredMask, out var information) != 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
if ((information.Mask & requiredMask) != requiredMask)
{
throw new PlatformNotSupportedException(
"The filesystem does not expose complete directory generation identity.");
}
statx landed in Linux 4.11. The interop targets the glibc wrapper (NativeInterop.cs:310), which on older kernels falls back to fstatat and returns success with STATX_BTIME absent, so the mask check at 235 throws on every call. DSM 7.x ships kernel 4.4 which is below 4.11.
DirectoryObjectIdentityResolver.cs:112 already classifies this correctly:
PlatformNotSupportedException => DirectoryObjectIdentityFailureKind.IdentityUnsupported,
and RootFolderStorageHealthResolver.cs:227-228 renders a clear message for it. But the scan path (ScanPathAuthorizationService.cs:288-321) doesn't go through the resolver, it surfaces the generic "The configured scan root no longer identifies its enrolled physical generation", which misdirects users into re-adding the root folder. That never helps: enrollment and validation share the capture path, so it fails at capture, not comparison.
Introduced in #717 (NativeOperations.cs added there; the requiredMask lines are new in that diff).
Reproduce
- Run Listenarr on a host with kernel < 4.11 (e.g., Synology DSM 7.x).
- Bind-mount a directory to
/audiobooks and add it as a root folder, this appears to succeed.
- Trigger a library scan and the scan fails, host stops, container restarts.
Diagnostics
$ uname -r
4.4.302+
$ docker exec listenarr stat -f -c 'fstype=%T' /audiobooks
fstype=btrfs
Run as the container's actual runtime user (PUID 1030 / PGID 100), not root, to rule out permissions:
$ docker exec -u 1030:100 listenarr stat -c 'dev=%d ino=%i birth=%W' /audiobooks
dev=46 ino=526181 birth=0
$ docker exec -u 1030:100 listenarr sh -c 'ls -la /audiobooks | head -3'
total 0
drwxrwxrwx 1 1026 users 1794 Aug 12 13:01 .
drwxr-xr-x 1 root root 236 Aug 12 12:57 ..
drwxrwxrwx 1 listenarr users 28 Oct 19 2025 Alan Bradley
The runtime user can open, stat, and traverse the directory, so this is not an access-permission failure. birth=0 is reported regardless of caller, which is expected: fstatat cannot supply a creation timestamp, so no combination of ownership or mode will satisfy STATX_BTIME on this kernel.
Summary
On hosts with a kernel older than 4.11,
STATX_BTIMEis unavailable, so directory identity capture fails. The codebase already models this outcome,DirectoryObjectIdentityFailureKind.IdentityUnsupported, with a user-facing message, but the scan authorization path doesn't route through it. ThePlatformNotSupportedExceptionescapes into aBackgroundService, and under .NET's defaultStopHostbehavior the entire host shuts down. The user sees an API 502 and a container restart loop instead of the intended "this storage location does not expose the directory identity Listenarr requires" message.Environment
ghcr.io/listenarrs/listenarr:canary, version1.3.04.4.302+; btrfs on/volume1/Music/Audiobooksbind-mounted to/audiobooksf27c798Mechanism
PinnedDirectoryCreation.NativeOperations.cs:223-247requires birth time:statxlanded in Linux 4.11. The interop targets the glibc wrapper (NativeInterop.cs:310), which on older kernels falls back tofstatatand returns success withSTATX_BTIMEabsent, so the mask check at 235 throws on every call. DSM 7.x ships kernel 4.4 which is below 4.11.DirectoryObjectIdentityResolver.cs:112already classifies this correctly:and
RootFolderStorageHealthResolver.cs:227-228renders a clear message for it. But the scan path (ScanPathAuthorizationService.cs:288-321) doesn't go through the resolver, it surfaces the generic "The configured scan root no longer identifies its enrolled physical generation", which misdirects users into re-adding the root folder. That never helps: enrollment and validation share the capture path, so it fails at capture, not comparison.Introduced in #717 (
NativeOperations.csadded there; therequiredMasklines are new in that diff).Reproduce
/audiobooksand add it as a root folder, this appears to succeed.Diagnostics
Run as the container's actual runtime user (PUID 1030 / PGID 100), not root, to rule out permissions:
The runtime user can open, stat, and traverse the directory, so this is not an access-permission failure.
birth=0is reported regardless of caller, which is expected:fstatatcannot supply a creation timestamp, so no combination of ownership or mode will satisfySTATX_BTIMEon this kernel.