Skip to content

fix(scanner): stop re-probing unchanged files ffprobe rejected - #2075

Merged
Quick104 merged 8 commits into
Silo-Server:mainfrom
blurbery:fix/scanner-skip-unchanged-probe-failures
Oct 10, 2026
Merged

Quick104 merged 8 commits into
Silo-Server:mainfrom
blurbery:fix/scanner-skip-unchanged-probe-failures

Conversation

@blurbery

@blurbery blurbery commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Related issue: N/A
Validation tasks: none recorded as passed on the board. The rehearsal runs reported on #1251 (C2, C3) and #1252 (C6) rescanned a corpus with damaged episodes and should be re-checked after merge; #1250 C1 and the #197 rescan case are not run yet.

A video file that ffprobe rejects is probed again on every scan of its library, even when the file hasn't changed. Each rescan runs ffprobe and the OSHash on the same bytes, gets the same rejection, and logs "scanner: ffprobe failed" again. #1810 added the probe_failed_at mark and noted this as a remaining limitation in its Risks section.

This change skips the probe repair for a rejected file while its size and modification time are the ones that were rejected.

The validation runs for #1251 C2 and C3 and #1252 C6 rescanned a corpus that includes zero-byte, random-byte and truncated episodes. Their outcomes should not change: the damaged files keep their mark and their "Damaged file" state, and only the repeated ffprobe and OSHash work on unchanged files goes away. Those cases should still be re-checked after merge, because the rescan path changes.

On my server, 2 files totalling 10.5 GiB are marked rejected and have never probed successfully. The scanner logged ffprobe failures for 3 files in 24 hours, one of them in two scans 6.5 hours apart. The cost grows with the number of broken files and how often their folders are scanned.

Root cause: the scan state that scanStateUpdateReasons reads (scanStateFile) never loaded probe_failed_at. A row with no probe metadata always counts as needing a playback-critical probe repair (needsCriticalProbeRepairScanState). So a rejected, unchanged file gets probe_repair on every scan and goes through the full path, ffprobe and OSHash included.

Approach

  • The scan state loads probe_failed_at.
  • A new scanStateNeedsProbeRepair skips the probe repair when all of these hold:
    • the row is marked rejected;
    • it has never probed successfully (probe_updated_at is null);
    • its size and modification time match the file on disk.
  • scanStateUpdateReasons and shouldSkipStableConfirmedScanState both use it, so they agree.
  • A rescan can still reach a marked file for another reason, such as a new subtitle sidecar. The probe then fails again, and the row's stored probe metadata must survive, as it does for a failed probe repair. Rows probed before rejections were tracked can carry such metadata. shouldPreserveExistingProbeAfterProbeFailure now treats a standing rejection like the repair reason.
  • The skip can't strand a file that would probe fine:
    • probe_failed_at is only set when ffprobe ran, read the file and rejected its content (IsProbeRejection). Timeouts, cancellations, and read or permission errors are never marked, so those files are still retried on every scan.
    • Changed bytes still produce size_changed or mtime_changed, which re-probe the file as before, and the upsert drops the mark.
    • A successful probe clears the mark. Playback through the native API still probes a marked file when it is played.
  • Review fixes:
    • Single-file scans (ScanFile) build scan state with scanStateFromMediaFile, which now carries probe_failed_at too, so the skip and the probe-fact preservation also apply there.
    • Extras get the same skip: an unchanged extra ffprobe rejected is no longer probed on every scan (extraFileUnchanged).
    • IsProbeRejection no longer counts exit status 126 or 127 as a rejection. Those come from the shell or the dynamic loader when ffprobe cannot start, for example when a shared library fails to load, and say nothing about the file.

Validation

  • go test ./internal/scanner/ -count=1: pass.

  • TestScanStateUpdateReasons_SkipsProbeRepairForRejectedUnchangedFile covers three cases:

    • a rejected, unchanged file gets no probe_repair and takes the stable-file skip;
    • the same file with a new modification time gets mtime_changed and probe_repair;
    • an unmarked file without probe data still gets probe_repair.

    On main (52096ba88) the test doesn't compile, because the scan state has no ProbeFailedAt: the rejection mark never reaches this decision, which is the bug.

  • TestRescanKeepsProbeFactsOfARejectedUnchangedFileDB, also listed in scripts/ci/db-contracts.txt, scans a rejected row that still has old probe facts, after a new subtitle sidecar appears, with an ffprobe that rejects it. The facts and the rejection must survive, and the subtitle must be recorded. On this branch's earlier head (69a680533) the facts came back empty ("" "" 0).

  • The whole internal/scanner package passes against a migrated local PostgreSQL 18 database, including the existing rejection tests (TestScanRecordsAndClearsProbeRejectionPostgres, TestUpsertKeepsProbeRejectionUntilProbedOrReplacedPostgres).

  • TestScanStateCarriesProbeRejectionDB is a new database contract, listed in scripts/ci/db-contracts.txt. It stores a rejected file, reads it back through GetScanStateByFolder (the query a library scan uses), and checks that a rescan of the unchanged file gets no probe_repair. It passes against a migrated local PostgreSQL 18 database, as do the scanner's other scan state database tests (scoped_library_state_test.go and TestFileRepositoryPreservesFirstSeenScanProvenance).

  • go vet ./internal/scanner/ and make lint-changed: clean.

  • Review fixes: TestScanStateFromMediaFileCarriesProbeRejection, TestExtraFileUnchangedSkipsRejectedExtra, and exit 126 and 127 cases in TestIsProbeRejectionSeparatesRefusedFilesFromProbeTrouble. Each fails without its fix.

  • Live check on an isolated dev server built from 38b6d0b28: a movie library with a random-byte movie, a random-byte trailer extra, and a random-byte file at the library root. ffprobe ran once per file. A second and third library scan, and a single-file scan of the root file, logged no further scanner: ffprobe failed and left the marks in place.

  • CI: c91f195a5 passed, including both new DB contract tests.

Benchmarks

Not measured. On an unchanged rescan, the change removes one ffprobe run and one OSHash read per rejected file. The scan-state query reads one more column.

Evidence

What a rejected file shows doesn't change: it keeps its "Damaged file" state, and the scanner stops probing it again while it is unchanged.

Two automated evidence notes on this PR describe other states:

  • Probe metadata cleared after a subtitle change: that was this branch's intermediate head 69a680533. It is fixed in 1e9d2d56c and covered by TestRescanKeepsProbeFactsOfARejectedUnchangedFileDB. With the fix, the file's stored probe metadata matches main.
  • A file that a newer ffprobe could read stays unreadable until it changes or is played: that's the trade-off under Risks. I haven't captured it, because it needs two ffprobe versions run against the same file.

The counts above came from read-only checks on my server, which runs my fork at 6ce7fb5cc.

Evidence: https://evidence.siloserver.org/r/silo-server/pr-2075/

Risks

  • If a future ffprobe version can read a file that today's version rejects, the scanner won't find out until the file changes, someone plays it, or the mark is cleared. Before, every rescan tried again.
  • Jellyfin-protocol playback doesn't run the probe repair, so a household that only uses Jellyfin clients has no playback path that retries a marked file.
  • No migration: probe_failed_at already exists (fix(playback): mark unreadable files and stop blaming the scan #1810).

Checklist

  • I read and can explain the complete diff.
  • This pull request addresses one concern.
  • The Evidence section shows every change a user can see, or says there is none.

AI Disclosure

  • Harness: Claude Code (desktop app)
  • Tool(s): Claude Code
  • Model(s): claude-opus-5-5
  • Involvement: AI-assisted
  • Adversarial review: I checked by hand which failures set the mark (IsProbeRejection, MarkProbeFailed and the upsert's probe_failed_at rule) before relying on it. The Kody review then found that a rescan for another reason cleared the stored probe metadata of a rejected legacy row. That is fixed and tested.

AI-assisted with Claude Opus. I directed the task and designed the work.

Review fixes (c03e1e5, dca9b58, 38b6d0b): Claude Code, claude-opus-5-5, with a cross-provider review by Codex (gpt-6.1-sol).

Note

Stop re-probing unchanged files whose ffprobe rejection still stands

  • The scanner now treats a stored ffprobe rejection as still valid when file size and modification time match and no later successful probe exists. Unchanged rejected files skip probing, skip the probe_repair update reason, and take the stable scan-state skip path in scanner.shouldSkipStableConfirmedScanState. See scanner.go.
  • Persisted probe metadata is preserved for a standing rejection, while identity and external-subtitle updates can still be written. Extras get the same treatment through the new scanner.extraFileUnchanged predicate in extras.go.
  • scanner.IsProbeRejection no longer classifies ffprobe exit statuses 126 and 127 (startup failures) as content rejections; other nonzero exits still use the stderr and bounded-read checks. See probe.go.
  • Adds probe_failed_at to the scan-state projection, row decoder, and single-file converter in scan_state.go, plus database-backed and unit tests registered in db-contracts.txt.
  • Behavioral Change: files that change size or modification time after a rejection still trigger probe repair; unchanged rejected files no longer record a new probe_failed_at on every scan.

Macroscope summarized 7de2231.

A file ffprobe rejects is marked probe_failed_at, but the scan state never
loaded that mark. Without probe metadata the file counts as needing a
probe repair on every scan, so each rescan ran ffprobe and the OSHash on
the same unchanged bytes again, and failed again.

The scan state now loads probe_failed_at. A marked file that never probed
successfully skips the probe repair while its size and modification time
are the ones that were rejected. Changed bytes drop the mark and are
probed as before, and playback still probes the file when it is played.
@silo-kody

silo-kody Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 14 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3300a83d-9056-4515-b1d8-44f7593c9808

📥 Commits

Reviewing files that changed from the base of the PR and between c91f195 and 7de2231.


📒 Files selected for processing (8)
  • internal/scanner/extras.go
  • internal/scanner/probe.go
  • internal/scanner/probe_rejection_test.go
  • internal/scanner/scan_state.go
  • internal/scanner/scan_state_probe_rejection_db_test.go
  • internal/scanner/scanner.go
  • internal/scanner/scanner_test.go
  • scripts/ci/db-contracts.txt

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 30499159-b8fc-4dc9-9e0c-280f85bf3d49



📥 Commits

Reviewing files that changed from the base of the PR and between ca186fe and c91f195.




📒 Files selected for processing (5)
  • internal/scanner/scan_state.go
  • internal/scanner/scan_state_probe_rejection_db_test.go
  • internal/scanner/scanner.go
  • internal/scanner/scanner_test.go
  • scripts/ci/db-contracts.txt



Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.





📝 Walkthrough
📝 Walkthrough
📝 Walkthrough

Walkthrough

The scanner records probe rejection timestamps in scan state. It skips repair probes for unchanged rejected files and can preserve existing probe facts when a failed probe coincides with identity or subtitle updates.

Changes

Probe rejection handling

Layer / File(s) Summary
Track rejection state and skip repeat repair probes
internal/scanner/scan_state.go, internal/scanner/scanner.go, internal/scanner/scanner_test.go, internal/scanner/scan_state_probe_rejection_db_test.go
Scan state includes ProbeFailedAt. Repair detection skips unchanged files with a recorded rejection and retains repair probing when file metadata changes or no rejection marker exists.
Preserve probe facts on failed rescans
internal/scanner/scanner.go, internal/scanner/scanner_test.go, internal/scanner/scan_state_probe_rejection_db_test.go, scripts/ci/db-contracts.txt
When a rejection still applies, a failed probe can preserve existing probe metadata while identity or subtitle changes are stored. Tests cover subtitle storage and retention of probe facts and rejection state.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: quick104





Merge Risk: ⚪ Minimal · up to c91f1

The change stops repeated ffprobe work for unchanged files that ffprobe has already rejected, and it keeps existing probe data when such a file is rescanned for other reasons. No actionable merge-blocking risk remains. CI and the database contract tests were still pending when the PR was written.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 4 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly and concisely summarizes the main change: preventing repeated probing of unchanged files rejected by ffprobe.
Description check Passed The description directly explains the scanner bug, implementation, behavior changes, tests, risks, and validation for the changeset.


Full details: Docstring Coverage

Explanation

Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 4 files. (1 skipped: 1 unsupported.)




✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR





  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Quick104 Quick104 added priority: P2 Limited scope, workaround exists, or polish impact: perf Unusable slowness on normal hardware labels Oct 8, 2026 — with Cursor
Comment thread internal/scanner/scanner.go
Adds a database contract that a rejected file's probe_failed_at reaches
the scan state a library scan reads, and that a rescan of the unchanged
file then gets no probe_repair reason.
@silo-kody

silo-kody Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

@macroscopeapp

macroscopeapp Bot commented Oct 8, 2026

Copy link
Copy Markdown

Visible effect without evidence: internal/scanner/scanner.go can clear stored codec, duration, track, and chapter metadata when an unchanged rejected file is rescanned after its external subtitles change; the Evidence section says there is no user-visible change and includes no captures. Please add before-and-after API response excerpts for the same item/request showing the affected fields (and client screenshots if these fields are rendered in a screen), naming the surface and commit.

Suggested fix: No diff; update the Evidence section with those captures.

Automated check: Macroscope check run agent (gpt-6-luna). Evidence was not reviewed for correctness.

Posted via Macroscope — Visible change evidence

@macroscopeapp

macroscopeapp Bot commented Oct 8, 2026

Copy link
Copy Markdown

The recorded Series — Web admin C2/C3 rescans (server 1555e5214; C2 also exercised #1954/#1955) need a re-check after merge. Those runs rescanned a corpus containing unchanged rejected episodes; this PR now skips repeat ffprobe/OSHash work for those files, changing the rescan path and duration while leaving the asserted library/history outcomes unchanged.

Suggested Validation tasks: line:
Validation tasks: changes #1251 C2/C3; changes #1250 C1; changes #197 rescan case

Automated check: Macroscope check run agent (gpt-6-luna). No validation was performed.

Posted via Macroscope — v1 validation impact

A row probed before rejections were tracked keeps its old probe facts
without probe_updated_at. Once it is marked rejected, a rescan for a
subtitle sidecar no longer carries probe_repair, so the failed probe
stopped counting as a repair and the full upsert cleared those facts.
A standing rejection now counts like the repair reason when deciding to
keep the stored probe metadata.
@silo-kody

silo-kody Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

@macroscopeapp

macroscopeapp Bot commented Oct 8, 2026

Copy link
Copy Markdown

An unchanged rejected file can remain unavailable after an ffprobe upgrade: internal/scanner/scanner.go skips rescanning it based on the old rejection mark and unchanged size/mtime, so newly readable metadata is not refreshed until playback or the mark is cleared. Please add before-and-after API response excerpts for the same item and request, comparing a rescan with the base commit and this commit under an ffprobe version that can read the file; name the surface and each commit.

Suggested fix: No diff; add the API response excerpts to the Evidence section.

Automated check: Macroscope check run agent (gpt-6-luna). Evidence was not reviewed for correctness.

Posted via Macroscope — Visible change evidence

A standing rejection never carries probe_repair, so every reason it has
is an identity or subtitle change; otherwise probe_repair is one of the
reasons and any second one needs the update. This drops the repeated
string literal the changed-line lint flagged.
@silo-kody

silo-kody Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

@Quick104 Quick104 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of c91f195a5. The approach is sound: the folder-scan path now leaves an unchanged rejected file alone, and the probe-fact preservation for a subtitle-triggered rescan holds. I found three gaps, two inline and one here. I'm fixing all three in commits on this branch, so there's no need to change anything yourself.

Rejected extras still re-probe on every scan (low). processExtraFiles (internal/scanner/extras.go, the unchanged check before gatherHints) only counts an extra as unchanged when ProbeUpdatedAt != nil. A damaged trailer in a Trailers/ folder is never probed successfully, so every scan of that folder runs ffprobe and the OSHash on it again, which is the cost this PR removes for primary files. Repair: also count it as unchanged while probeRejectionStands holds for that row.

Also checked, no change needed:

  • Native playback still re-probes a marked file before the planner refuses it (ensurePlaybackProbe runs ensureProbeRepair before PlanPlaybackV3 checks ProbeRejected). Jellyfin-protocol playback has no such probe, so for a Jellyfin-only household the "playback still probes it" fallback doesn't apply. That is worth one line under Risks.
  • The Kody thread's fix is in place, and both new DB contract tests passed in CI on this head.

AI-assisted review: Claude Code, claude-opus-5-5, with a cross-provider pass by Codex (gpt-6.1-sol). I checked each finding against the source.

Comment thread internal/scanner/scan_state.go
Comment thread internal/scanner/scanner.go
Quick104 and others added 3 commits October 10, 2026 00:47
ScanFile builds its scan state from the stored row, and that conversion
dropped probe_failed_at, so a targeted scan of an unchanged rejected file
still probed it again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The extras unchanged check required a successful probe, so a damaged extra
was probed again on every scan. Treat a standing rejection as unchanged,
and test the single-file scan state conversion too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exit status 126 or 127 comes from the shell or the dynamic loader, not from
ffprobe refusing the file. Scans now skip marked files, so such a mark
would no longer be retried.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@silo-kody

silo-kody Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

Resolve the db-contracts.txt list conflict by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Quick104

Copy link
Copy Markdown
Contributor

Review summary

Reviewed head: 7de2231c9 (the fixes below, plus a merge of main to clear a conflict in scripts/ci/db-contracts.txt). CI: every Go, DB contract and lint job passed, including both new DB contract tests. Web (build) fails on the launch bundle budget, the same 1214 B overage main fails on at ade836a2d, tracked in #2203. This branch changes no web code.

Fit: 9/10 · Readiness: 8/10 — conditional

Fit: the change stops the scanner from running ffprobe and the OSHash again on files whose bytes it has already rejected. Rescans cost nothing for those files, no setting is added, and the decision sits in the scanner's scan-state check, where the other "does this file need work" decisions already live. The one trade-off, that a rejection now holds until the file changes or is played, is disclosed under Risks.

Readiness: no substantive defect remains after the fixes below, and they are tested and checked live. It is conditional because the required CI result check fails until #2203 lands on main and this branch picks it up, and because of the retry question under Needs your call.

Fixed on this branch

  • Single-file scans ignored the mark (c03e1e5). scanStateFromMediaFile didn't copy probe_failed_at, so a scan of one path (ScanFile) still re-probed the file, and on a subtitle change it could still clear a legacy row's probe facts.
  • Rejected extras re-probed on every scan (dca9b58). The extras unchanged check now treats a standing rejection as unchanged (extraFileUnchanged).
  • ffprobe that couldn't start marked files rejected (38b6d0b). Exit status 126 or 127 (for example, the dynamic loader failing to map a shared library) is no longer a rejection. Before this PR such marks cleared on the next scan; with the skip they would have stuck.

Each fix has a test that fails without it. go test ./internal/scanner/, go vet and make lint-changed pass. On an isolated dev server built from 38b6d0b28, a movie library with a random-byte movie, a random-byte trailer extra, and a random-byte file at the library root logged one scanner: ffprobe failed per file. Two more library scans and a single-file scan logged none, and the marks stayed in place.

Needs your call

  • How a rejection gets retried. The mark is keyed to size and modification time only. After an ffprobe upgrade, a file the new version can read stays "Unreadable" until it changes or someone plays it through the native API. Jellyfin-protocol playback doesn't run the probe repair, so Jellyfin-only households have no retry path. Options: accept this as documented; store the ffprobe version with the mark and treat a different version as a reason to probe again (needs a migration); or have scans retry marks older than some age.

Other feedback

  • The Kody finding about cleared probe facts was fixed by the author in 1e9d2d5. I confirmed the fix and its test.
  • Macroscope's two evidence notes: the first describes the intermediate head 69a680533, which is fixed; the second is the ffprobe-upgrade trade-off above.
  • Not filed: the probe-failure preserve path writes identity and subtitles through UpdateIdentityAndExternalSubtitles, which leaves extra_id and file_hash alone. That predates this PR for rejected rows. The PR extends it to scanners without ffprobe and to legacy rows with probe facts. The trigger (a damaged file reclassified from an extra, or a rejected row with no OSHash, plus an identity change) is narrow, and I didn't reproduce it.

Validated features

No case recorded as passed on the v1.0 board reaches this change. The board lists #1251 C2 and C3 and #1252 C6 as not run. The rehearsal results posted on those issues rescanned a corpus with damaged episodes and should be re-checked after merge, as the PR body says. I updated the Validation tasks: line to match the board.

Clients and docs

  • No API, wire-format or jellycompat response change. The "Unreadable" flag behaves as before. Apple and Android need no follow-up.
  • User manual: unaffected. No page on siloserver.org describes probe retries or damaged-file rescans.

AI-assisted review: Claude Code (claude-opus-5-5), with a cross-provider round by Codex (gpt-6.1-sol). Codex's three findings: the single-file gap (fixed), exit-127 rejections (fixed), and a catalog-seed overwrite case I dropped because it needs a replacement file whose size matches the seed while its mtime equals the old row's.

@Quick104
Quick104 merged commit bf63b3d into Silo-Server:main Oct 10, 2026
18 of 20 checks passed
@blurbery

blurbery commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor Author

On the retry question from your review: I'd store the ffprobe version with the rejection and probe the file again when the version changes. That covers an ffprobe upgrade without re-probing broken files on a timer, and because the scan does the re-probe, Jellyfin-only households get a retry path too. It would need a small migration.

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

Labels

impact: perf Unusable slowness on normal hardware priority: P2 Limited scope, workaround exists, or polish

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants