Skip to content

fix(files): file registration records the proc-fd link size (64 bytes) instead of the real file length - #901

Open
kevinheneveld wants to merge 1 commit into
Listenarrs:canaryfrom
kevinheneveld:fix/proc-fd-file-size
Open

kevinheneveld wants to merge 1 commit into
Listenarrs:canaryfrom
kevinheneveld:fix/proc-fd-file-size

Conversation

@kevinheneveld

Copy link
Copy Markdown
Contributor

The bug

Since #717, AudiobookFileService reads the registered file's size from the lease's metadata path (new FileInfo(metadataPath).Length). On Linux that path is a /proc/self/fd/<n> magic link, and FileSystemInfo stats the link inode itself — proc magic symlinks report a constant st_size of 64. Result: every file registered through a pinned lease persists Size = 64, regardless of the real file.

Live evidence from my instance (v1.3.0): 361 of 27,912 AudiobookFiles rows have Size = 64 exactly — every one created after the deploy that brought in #717 (sources: download, LibraryScan, MetadataRescan). The UI shows "0 KB" wherever sizes render (the duplicate-ASIN comparison table is where I caught it).

The fix

Open the metadata path and take the stream length. Opening follows the magic link to the exact pinned object the lease holds open, so the size is read from the real file without reintroducing the rename race the metadata path exists to avoid. Applied at both write sites:

  • AudiobookFileService.cs — initial registration (Size = null when the open fails, as before)
  • AudiobookFileService.PhysicalGeneration.cs — generation-replacement snapshots (falls back to the previously recorded size)

Notes

  • ExtractMetadataAsync's cache key uses LastWriteTimeUtc of the same metadata path, which has the same lstat behavior on Linux (the link's mtime, not the file's). It only weakens cache invalidation — the key still includes the physical object identity — so I left it out of scope here; happy to fold it in if you'd like.
  • Existing rows with Size = 64 stay wrong until re-registered; on my instance I'm backfilling them from disk. If you want, a follow-up startup reconciliation could repair Size = 64 rows whose path resolves, but I didn't want to bundle that policy decision into this fix.

🤖 Generated with Claude Code

…) instead of the real file length

Since Listenarrs#717, file registration reads sizes from the lease's metadata path,
which on Linux is a /proc/self/fd magic link. FileSystemInfo.Length reads
the link inode itself (lstat), and proc magic symlinks report a constant
st_size of 64 — so every file registered through a pinned lease persists
Size = 64 regardless of the actual file. The UI then shows 0 KB wherever
sizes are rendered (duplicate comparison, library size totals, quality
scoring inputs).

Open the metadata path instead and take the stream length: opening follows
the magic link to the exact pinned object the lease holds, so the length
is read from the real file without reintroducing the rename race the
metadata path exists to avoid. Applies to both initial registration and
physical-generation replacement snapshots (the latter falls back to the
prior recorded size when the open fails).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kevinheneveld
kevinheneveld requested a review from a team August 25, 2026 21:45
kevinheneveld added a commit to kevinheneveld/Listenarr that referenced this pull request Aug 26, 2026
…fd link (PR Listenarrs#901)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 26, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 27, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 28, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 28, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 31, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 31, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 1, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 1, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 1, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 1, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 3, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 3, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 3, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 4, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 4, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 9, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 15, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 0d4e045)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 19, 2026
…ts beside Listenarrs#901

No behaviour change. The gate and its reasoning move from EnsureAudiobookFileCoreAsync
into QualifiesAsAudioContent in a new AudiobookFileService.ContentGate.cs, following the
six partials this class already uses. The call site is four lines.

Why it matters beyond tidiness. BackendArchitectureTests.ActiveProductionSourceFiles_RemainFocused
fails any production file over 500 lines. AudiobookFileService.cs is 469 on canary, so there
are 31 lines of headroom and two open PRs are spending them:

    canary                   469
    Listenarrs#901 alone               494   passes
    this branch before       487   passes
    both together            512   FAILS

Measured by merging this branch onto Listenarrs#901 head 58adddd: 3141 passed, 130 skipped, and that
one architecture test failed. Each PR passes the guard alone and the pair does not, so whoever
merged second would have hit a failure they could not see from their own branch.

This branch's footprint in the shared file drops from 18 lines to 5, which puts the combination
at 499. Suite unchanged at 3142 passed / 0 failed / 130 skipped, and the application project
rebuilds at zero warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 0d4e045)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…ts beside Listenarrs#901

No behaviour change. The gate and its reasoning move from EnsureAudiobookFileCoreAsync
into QualifiesAsAudioContent in a new AudiobookFileService.ContentGate.cs, following the
six partials this class already uses. The call site is four lines.

Why it matters beyond tidiness. BackendArchitectureTests.ActiveProductionSourceFiles_RemainFocused
fails any production file over 500 lines. AudiobookFileService.cs is 469 on canary, so there
are 31 lines of headroom and two open PRs are spending them:

    canary                   469
    Listenarrs#901 alone               494   passes
    this branch before       487   passes
    both together            512   FAILS

Measured by merging this branch onto Listenarrs#901 head 58adddd: 3141 passed, 130 skipped, and that
one architecture test failed. Each PR passes the guard alone and the pair does not, so whoever
merged second would have hit a failure they could not see from their own branch.

This branch's footprint in the shared file drops from 18 lines to 5, which puts the combination
at 499. Suite unchanged at 3142 passed / 0 failed / 130 skipped, and the application project
rebuilds at zero warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit e68f6db)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 0d4e045)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…ts beside Listenarrs#901

No behaviour change. The gate and its reasoning move from EnsureAudiobookFileCoreAsync
into QualifiesAsAudioContent in a new AudiobookFileService.ContentGate.cs, following the
six partials this class already uses. The call site is four lines.

Why it matters beyond tidiness. BackendArchitectureTests.ActiveProductionSourceFiles_RemainFocused
fails any production file over 500 lines. AudiobookFileService.cs is 469 on canary, so there
are 31 lines of headroom and two open PRs are spending them:

    canary                   469
    Listenarrs#901 alone               494   passes
    this branch before       487   passes
    both together            512   FAILS

Measured by merging this branch onto Listenarrs#901 head 58adddd: 3141 passed, 130 skipped, and that
one architecture test failed. Each PR passes the guard alone and the pair does not, so whoever
merged second would have hit a failure they could not see from their own branch.

This branch's footprint in the shared file drops from 18 lines to 5, which puts the combination
at 499. Suite unchanged at 3142 passed / 0 failed / 130 skipped, and the application project
rebuilds at zero warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit e68f6db)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…ests

Review nit on item 236 (Listenarrs#901): both new tests already assert MetadataPath
looks like a real /proc/<pid>/fd/<n> link, but nothing checked that lstat
(FileInfo.Length) on that path actually disagrees with the real file, the
way the removed DivergentDescriptorFixture control did. Without that, a
runtime change that made lstat follow the link -- so the 64-byte bug simply
stopped reproducing -- could make either test pass without
TryGetRegisteredFileLength doing anything, since both stat and open would
already agree.

Added the same control inline: Assert.NotEqual between the real byte count
and new FileInfo(MetadataPath).Length, right after the /proc path shape
assertion and before the registration/refresh call.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 0d4e045)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…ts beside Listenarrs#901

No behaviour change. The gate and its reasoning move from EnsureAudiobookFileCoreAsync
into QualifiesAsAudioContent in a new AudiobookFileService.ContentGate.cs, following the
six partials this class already uses. The call site is four lines.

Why it matters beyond tidiness. BackendArchitectureTests.ActiveProductionSourceFiles_RemainFocused
fails any production file over 500 lines. AudiobookFileService.cs is 469 on canary, so there
are 31 lines of headroom and two open PRs are spending them:

    canary                   469
    Listenarrs#901 alone               494   passes
    this branch before       487   passes
    both together            512   FAILS

Measured by merging this branch onto Listenarrs#901 head 58adddd: 3141 passed, 130 skipped, and that
one architecture test failed. Each PR passes the guard alone and the pair does not, so whoever
merged second would have hit a failure they could not see from their own branch.

This branch's footprint in the shared file drops from 18 lines to 5, which puts the combination
at 499. Suite unchanged at 3142 passed / 0 failed / 130 skipped, and the application project
rebuilds at zero warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit e68f6db)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…t a two-file double

The carried-forward test (dc84254, from our own narrower fix) modeled the
lease's MetadataPath as a second, independent 64-byte file standing in for a
/proc/<pid>/fd/<n> magic link. That is faithful to a fix that reads size
through the lease's own OpenMetadataReadStream(), which is what our fix did.
Listenarrs#901 does not call that: TryGetRegisteredFileLength opens MetadataPath
itself. A real magic link, opened directly, resolves to the pinned file's
real bytes; only lstat'ing it (FileInfo.Length, the original bug) reports a
constant 64. A second, unrelated file at MetadataPath cannot happen with a
real lease, so the two-file double doesn't exercise what Listenarrs#901 actually does,
and both of its behavioural tests still failed (64 instead of the real
length) with Listenarrs#901's fix applied -- not because the bug persists, but because
the double isn't the bug.

Replaced with two tests built on the production
PinnedAudiobookFileRegistrationLease.Open(), already used elsewhere in this
file, so MetadataPath is a real /proc/<pid>/fd/<n> link on this Linux box:
one for EnsureAudiobookFileAsync (the original AudiobookFileService.cs site)
and one for RefreshPhysicalGenerationAsync (the PhysicalGeneration.cs site
Listenarrs#901 also touches, which had no dedicated size test before). Measured
fail-first on canary-m4bard without Listenarrs#901 (both report 64, matching the real
FileInfo/lstat bug on this box), and pass with Listenarrs#901's fix applied.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f279b5f)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…ests

Review nit on item 236 (Listenarrs#901): both new tests already assert MetadataPath
looks like a real /proc/<pid>/fd/<n> link, but nothing checked that lstat
(FileInfo.Length) on that path actually disagrees with the real file, the
way the removed DivergentDescriptorFixture control did. Without that, a
runtime change that made lstat follow the link -- so the 64-byte bug simply
stopped reproducing -- could make either test pass without
TryGetRegisteredFileLength doing anything, since both stat and open would
already agree.

Added the same control inline: Assert.NotEqual between the real byte count
and new FileInfo(MetadataPath).Length, right after the /proc path shape
assertion and before the registration/refresh call.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6e3e188)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 0d4e045)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…ts beside Listenarrs#901

No behaviour change. The gate and its reasoning move from EnsureAudiobookFileCoreAsync
into QualifiesAsAudioContent in a new AudiobookFileService.ContentGate.cs, following the
six partials this class already uses. The call site is four lines.

Why it matters beyond tidiness. BackendArchitectureTests.ActiveProductionSourceFiles_RemainFocused
fails any production file over 500 lines. AudiobookFileService.cs is 469 on canary, so there
are 31 lines of headroom and two open PRs are spending them:

    canary                   469
    Listenarrs#901 alone               494   passes
    this branch before       487   passes
    both together            512   FAILS

Measured by merging this branch onto Listenarrs#901 head 58adddd: 3141 passed, 130 skipped, and that
one architecture test failed. Each PR passes the guard alone and the pair does not, so whoever
merged second would have hit a failure they could not see from their own branch.

This branch's footprint in the shared file drops from 18 lines to 5, which puts the combination
at 499. Suite unchanged at 3142 passed / 0 failed / 130 skipped, and the application project
rebuilds at zero warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit e68f6db)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…t a two-file double

The carried-forward test (dc84254, from our own narrower fix) modeled the
lease's MetadataPath as a second, independent 64-byte file standing in for a
/proc/<pid>/fd/<n> magic link. That is faithful to a fix that reads size
through the lease's own OpenMetadataReadStream(), which is what our fix did.
Listenarrs#901 does not call that: TryGetRegisteredFileLength opens MetadataPath
itself. A real magic link, opened directly, resolves to the pinned file's
real bytes; only lstat'ing it (FileInfo.Length, the original bug) reports a
constant 64. A second, unrelated file at MetadataPath cannot happen with a
real lease, so the two-file double doesn't exercise what Listenarrs#901 actually does,
and both of its behavioural tests still failed (64 instead of the real
length) with Listenarrs#901's fix applied -- not because the bug persists, but because
the double isn't the bug.

Replaced with two tests built on the production
PinnedAudiobookFileRegistrationLease.Open(), already used elsewhere in this
file, so MetadataPath is a real /proc/<pid>/fd/<n> link on this Linux box:
one for EnsureAudiobookFileAsync (the original AudiobookFileService.cs site)
and one for RefreshPhysicalGenerationAsync (the PhysicalGeneration.cs site
Listenarrs#901 also touches, which had no dedicated size test before). Measured
fail-first on canary-m4bard without Listenarrs#901 (both report 64, matching the real
FileInfo/lstat bug on this box), and pass with Listenarrs#901's fix applied.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f279b5f)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 29, 2026
…ests

Review nit on item 236 (Listenarrs#901): both new tests already assert MetadataPath
looks like a real /proc/<pid>/fd/<n> link, but nothing checked that lstat
(FileInfo.Length) on that path actually disagrees with the real file, the
way the removed DivergentDescriptorFixture control did. Without that, a
runtime change that made lstat follow the link -- so the 64-byte bug simply
stopped reproducing -- could make either test pass without
TryGetRegisteredFileLength doing anything, since both stat and open would
already agree.

Added the same control inline: Assert.NotEqual between the real byte count
and new FileInfo(MetadataPath).Length, right after the /proc path shape
assertion and before the registration/refresh call.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6e3e188)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 1, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 0d4e045)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 1, 2026
…ts beside Listenarrs#901

No behaviour change. The gate and its reasoning move from EnsureAudiobookFileCoreAsync
into QualifiesAsAudioContent in a new AudiobookFileService.ContentGate.cs, following the
six partials this class already uses. The call site is four lines.

Why it matters beyond tidiness. BackendArchitectureTests.ActiveProductionSourceFiles_RemainFocused
fails any production file over 500 lines. AudiobookFileService.cs is 469 on canary, so there
are 31 lines of headroom and two open PRs are spending them:

    canary                   469
    Listenarrs#901 alone               494   passes
    this branch before       487   passes
    both together            512   FAILS

Measured by merging this branch onto Listenarrs#901 head 58adddd: 3141 passed, 130 skipped, and that
one architecture test failed. Each PR passes the guard alone and the pair does not, so whoever
merged second would have hit a failure they could not see from their own branch.

This branch's footprint in the shared file drops from 18 lines to 5, which puts the combination
at 499. Suite unchanged at 3142 passed / 0 failed / 130 skipped, and the application project
rebuilds at zero warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit e68f6db)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 1, 2026
…t a two-file double

The carried-forward test (dc84254, from our own narrower fix) modeled the
lease's MetadataPath as a second, independent 64-byte file standing in for a
/proc/<pid>/fd/<n> magic link. That is faithful to a fix that reads size
through the lease's own OpenMetadataReadStream(), which is what our fix did.
Listenarrs#901 does not call that: TryGetRegisteredFileLength opens MetadataPath
itself. A real magic link, opened directly, resolves to the pinned file's
real bytes; only lstat'ing it (FileInfo.Length, the original bug) reports a
constant 64. A second, unrelated file at MetadataPath cannot happen with a
real lease, so the two-file double doesn't exercise what Listenarrs#901 actually does,
and both of its behavioural tests still failed (64 instead of the real
length) with Listenarrs#901's fix applied -- not because the bug persists, but because
the double isn't the bug.

Replaced with two tests built on the production
PinnedAudiobookFileRegistrationLease.Open(), already used elsewhere in this
file, so MetadataPath is a real /proc/<pid>/fd/<n> link on this Linux box:
one for EnsureAudiobookFileAsync (the original AudiobookFileService.cs site)
and one for RefreshPhysicalGenerationAsync (the PhysicalGeneration.cs site
Listenarrs#901 also touches, which had no dedicated size test before). Measured
fail-first on canary-m4bard without Listenarrs#901 (both report 64, matching the real
FileInfo/lstat bug on this box), and pass with Listenarrs#901's fix applied.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f279b5f)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 1, 2026
…ests

Review nit on item 236 (Listenarrs#901): both new tests already assert MetadataPath
looks like a real /proc/<pid>/fd/<n> link, but nothing checked that lstat
(FileInfo.Length) on that path actually disagrees with the real file, the
way the removed DivergentDescriptorFixture control did. Without that, a
runtime change that made lstat follow the link -- so the 64-byte bug simply
stopped reproducing -- could make either test pass without
TryGetRegisteredFileLength doing anything, since both stat and open would
already agree.

Added the same control inline: Assert.NotEqual between the real byte count
and new FileInfo(MetadataPath).Length, right after the /proc path shape
assertion and before the registration/refresh call.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6e3e188)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 1, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 0d4e045)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 1, 2026
…ts beside Listenarrs#901

No behaviour change. The gate and its reasoning move from EnsureAudiobookFileCoreAsync
into QualifiesAsAudioContent in a new AudiobookFileService.ContentGate.cs, following the
six partials this class already uses. The call site is four lines.

Why it matters beyond tidiness. BackendArchitectureTests.ActiveProductionSourceFiles_RemainFocused
fails any production file over 500 lines. AudiobookFileService.cs is 469 on canary, so there
are 31 lines of headroom and two open PRs are spending them:

    canary                   469
    Listenarrs#901 alone               494   passes
    this branch before       487   passes
    both together            512   FAILS

Measured by merging this branch onto Listenarrs#901 head 58adddd: 3141 passed, 130 skipped, and that
one architecture test failed. Each PR passes the guard alone and the pair does not, so whoever
merged second would have hit a failure they could not see from their own branch.

This branch's footprint in the shared file drops from 18 lines to 5, which puts the combination
at 499. Suite unchanged at 3142 passed / 0 failed / 130 skipped, and the application project
rebuilds at zero warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit e68f6db)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 1, 2026
…t a two-file double

The carried-forward test (dc84254, from our own narrower fix) modeled the
lease's MetadataPath as a second, independent 64-byte file standing in for a
/proc/<pid>/fd/<n> magic link. That is faithful to a fix that reads size
through the lease's own OpenMetadataReadStream(), which is what our fix did.
Listenarrs#901 does not call that: TryGetRegisteredFileLength opens MetadataPath
itself. A real magic link, opened directly, resolves to the pinned file's
real bytes; only lstat'ing it (FileInfo.Length, the original bug) reports a
constant 64. A second, unrelated file at MetadataPath cannot happen with a
real lease, so the two-file double doesn't exercise what Listenarrs#901 actually does,
and both of its behavioural tests still failed (64 instead of the real
length) with Listenarrs#901's fix applied -- not because the bug persists, but because
the double isn't the bug.

Replaced with two tests built on the production
PinnedAudiobookFileRegistrationLease.Open(), already used elsewhere in this
file, so MetadataPath is a real /proc/<pid>/fd/<n> link on this Linux box:
one for EnsureAudiobookFileAsync (the original AudiobookFileService.cs site)
and one for RefreshPhysicalGenerationAsync (the PhysicalGeneration.cs site
Listenarrs#901 also touches, which had no dedicated size test before). Measured
fail-first on canary-m4bard without Listenarrs#901 (both report 64, matching the real
FileInfo/lstat bug on this box), and pass with Listenarrs#901's fix applied.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f279b5f)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 1, 2026
…ests

Review nit on item 236 (Listenarrs#901): both new tests already assert MetadataPath
looks like a real /proc/<pid>/fd/<n> link, but nothing checked that lstat
(FileInfo.Length) on that path actually disagrees with the real file, the
way the removed DivergentDescriptorFixture control did. Without that, a
runtime change that made lstat follow the link -- so the 64-byte bug simply
stopped reproducing -- could make either test pass without
TryGetRegisteredFileLength doing anything, since both stat and open would
already agree.

Added the same control inline: Assert.NotEqual between the real byte count
and new FileInfo(MetadataPath).Length, right after the /proc path shape
assertion and before the registration/refresh call.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6e3e188)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 0d4e045)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 2, 2026
…ts beside Listenarrs#901

No behaviour change. The gate and its reasoning move from EnsureAudiobookFileCoreAsync
into QualifiesAsAudioContent in a new AudiobookFileService.ContentGate.cs, following the
six partials this class already uses. The call site is four lines.

Why it matters beyond tidiness. BackendArchitectureTests.ActiveProductionSourceFiles_RemainFocused
fails any production file over 500 lines. AudiobookFileService.cs is 469 on canary, so there
are 31 lines of headroom and two open PRs are spending them:

    canary                   469
    Listenarrs#901 alone               494   passes
    this branch before       487   passes
    both together            512   FAILS

Measured by merging this branch onto Listenarrs#901 head 58adddd: 3141 passed, 130 skipped, and that
one architecture test failed. Each PR passes the guard alone and the pair does not, so whoever
merged second would have hit a failure they could not see from their own branch.

This branch's footprint in the shared file drops from 18 lines to 5, which puts the combination
at 499. Suite unchanged at 3142 passed / 0 failed / 130 skipped, and the application project
rebuilds at zero warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit e68f6db)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 2, 2026
…t a two-file double

The carried-forward test (dc84254, from our own narrower fix) modeled the
lease's MetadataPath as a second, independent 64-byte file standing in for a
/proc/<pid>/fd/<n> magic link. That is faithful to a fix that reads size
through the lease's own OpenMetadataReadStream(), which is what our fix did.
Listenarrs#901 does not call that: TryGetRegisteredFileLength opens MetadataPath
itself. A real magic link, opened directly, resolves to the pinned file's
real bytes; only lstat'ing it (FileInfo.Length, the original bug) reports a
constant 64. A second, unrelated file at MetadataPath cannot happen with a
real lease, so the two-file double doesn't exercise what Listenarrs#901 actually does,
and both of its behavioural tests still failed (64 instead of the real
length) with Listenarrs#901's fix applied -- not because the bug persists, but because
the double isn't the bug.

Replaced with two tests built on the production
PinnedAudiobookFileRegistrationLease.Open(), already used elsewhere in this
file, so MetadataPath is a real /proc/<pid>/fd/<n> link on this Linux box:
one for EnsureAudiobookFileAsync (the original AudiobookFileService.cs site)
and one for RefreshPhysicalGenerationAsync (the PhysicalGeneration.cs site
Listenarrs#901 also touches, which had no dedicated size test before). Measured
fail-first on canary-m4bard without Listenarrs#901 (both report 64, matching the real
FileInfo/lstat bug on this box), and pass with Listenarrs#901's fix applied.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f279b5f)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Oct 2, 2026
…ests

Review nit on item 236 (Listenarrs#901): both new tests already assert MetadataPath
looks like a real /proc/<pid>/fd/<n> link, but nothing checked that lstat
(FileInfo.Length) on that path actually disagrees with the real file, the
way the removed DivergentDescriptorFixture control did. Without that, a
runtime change that made lstat follow the link -- so the 64-byte bug simply
stopped reproducing -- could make either test pass without
TryGetRegisteredFileLength doing anything, since both stat and open would
already agree.

Added the same control inline: Assert.NotEqual between the real byte count
and new FileInfo(MetadataPath).Length, right after the /proc path shape
assertion and before the registration/refresh call.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6e3e188)
@m4bard

m4bard commented Oct 2, 2026

Copy link
Copy Markdown

I ran part of this today against our own fork's build, carrying this PR's commit alongside other unrelated fork patches, not a stock canary instance. What I have below is evidence of how real the underlying bug is, plus a test-double finding, not confirmation that the fix itself works end to end on a live install. More on why further down. As far as I can tell the diagnosis is specific and the fix matches it: reading the metadata path instead of lstat'ing it is exactly what's needed once you know a /proc/<pid>/fd/<n> magic link always reports a constant 64-byte lstat size, regardless of the real file behind it.

On the backfill question the PR body already flags: rows written before this fix was in place currently carry Size = 64 on this install, a large fraction of registered audio files, which is consistent with the diagnosis at real scale rather than just the small repro in the PR description. I didn't get to test whether re-registering one of those rows corrects it, so I can't say anything either way about that part.

I wasn't able to finish the other half of the check this pass, whether a freshly scanned file gets the correct size going forward. That's blocked by something unrelated to this PR on this particular install, not a problem with the fix itself.

One thing on the test side: we wrote our own regression test for this on our branch, and the first version modeled the lease's MetadataPath as a second, ordinary file standing in for the /proc/<pid>/fd/<n> link rather than a real one. That double doesn't reproduce the actual lstat-on-a-magic-link behavior, since a plain file doesn't carry the constant 64-byte lstat quirk a proc magic link does, so the test was passing for the wrong reason. We replaced it with a version that opens a genuine /proc/<pid>/fd/<n> link to a real file, plus an assertion that lstat on that link actually disagrees with the real file's length, so the test proves its own setup reproduces the quirk before it asserts anything about the fix. I don't see a test in this PR's diff; happy to share ours if it's useful, no pressure either way.

Unless I'm missing something, this looks solid. Does the backfill behavior for existing rows match what you'd expect, or is there a migration or re-scan path already planned for those?

Disclosure: drafted with Claude Code at my direction; I read the cited code at the stated commit and reviewed this before posting.

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.

2 participants