docs(jellyfin): the door that scans is /Library/Refresh, and the art door names its field #120

Merged
dank merged 4 commits from docs/jellyfin-scan-door into main 2026-11-02 07:16:53 -08:00
Owner

What

The Jellyfin page told the next agent to fire POST /Items/{id}/Refresh?Recursive=true
per library to make a new folder visible. Measured 2026-11-02 against a Shows library
that had never been identified: that route answers 204 and queues nothing —
item census byte-identical before and after, ScheduledTasks idle, the journal silent,
and still a silent 204 after a full service restart. RefreshStatus on the
VirtualFolders record stays Idle even during a real scan, so it is not a progress read.

The door that actually works is POST /Library/Refresh (204), and progress is read from
GET /ScheduledTasks → Scan Media Library → State.CurrentProgressPercentage.

Evidence

Probe Result
POST /Items/{id}/Refresh?Recursive=true (per library) 204, no task, census unchanged, before and after systemctl --user restart jellyfin.service
POST /Library/Refresh 204, Scan Media Library → Running, 202 % CPU, journal names folders under data/TVShows/…, EpisodeMetadataService probing episodes
VirtualFolders.RefreshStatus during that real scan Idle
ScheduledTasks during that real scan Scan Media Library Running 75→80 %

Also recorded, because it is how the wrong claim got believed: a 204 from an accepted-but-
discarded refresh is indistinguishable from a 204 that queued work. The acceptance read
is the journal naming folders, plus the item census moving.

Provider note: the scan performs identify, but image delivery is a separate door —
GET /Items/{id}/RemoteImages?Type=Primary (the field is Url, not ImageUrl) then
POST /Items/{id}/RemoteImages/Download?type=…&imageUrl=… → 204 stores the tag. PascalCase
params 500; a missing imageUrl 500s, which is how a wrong field name reads as a server bug.


cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-18b1-7573-bcd5-ac99636f8bcb

## What The Jellyfin page told the next agent to fire `POST /Items/{id}/Refresh?Recursive=true` per library to make a new folder visible. Measured 2026-11-02 against a `Shows` library that had **never** been identified: that route answers **204 and queues nothing** — item census byte-identical before and after, `ScheduledTasks` idle, the journal silent, and still a silent 204 after a full service restart. `RefreshStatus` on the VirtualFolders record stays `Idle` even during a real scan, so it is not a progress read. The door that actually works is `POST /Library/Refresh` (204), and progress is read from `GET /ScheduledTasks` → `Scan Media Library` → `State.CurrentProgressPercentage`. ## Evidence | Probe | Result | |---|---| | `POST /Items/{id}/Refresh?Recursive=true` (per library) | 204, no task, census unchanged, before and after `systemctl --user restart jellyfin.service` | | `POST /Library/Refresh` | 204, `Scan Media Library` → `Running`, 202 % CPU, journal names folders under `data/TVShows/…`, `EpisodeMetadataService` probing episodes | | `VirtualFolders.RefreshStatus` during that real scan | `Idle` | | `ScheduledTasks` during that real scan | `Scan Media Library Running 75→80 %` | Also recorded, because it is how the wrong claim got believed: a 204 from an accepted-but- discarded refresh is indistinguishable from a 204 that queued work. The acceptance read is the journal naming folders, plus the item census moving. Provider note: the scan performs identify, but image delivery is a separate door — `GET /Items/{id}/RemoteImages?Type=Primary` (the field is `Url`, **not** `ImageUrl`) then `POST /Items/{id}/RemoteImages/Download?type=…&imageUrl=…` → 204 stores the tag. PascalCase params 500; a missing `imageUrl` 500s, which is how a wrong field name reads as a server bug. --- cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-18b1-7573-bcd5-ac99636f8bcb
dank added 1 commit 2026-11-02 02:56:48 -08:00
The page sent the next agent to POST /Items/{id}/Refresh?Recursive=true per
library to make a new folder visible. Measured against a Shows library that had
never been identified: that route answers 204 and queues nothing — item census
byte-identical, ScheduledTasks idle, journal silent, and still a silent 204 after
a service restart. The door that works is POST /Library/Refresh, and the progress
read is ScheduledTasks -> Scan Media Library -> CurrentProgressPercentage.
RefreshStatus on the VirtualFolders record stays Idle through a real scan, so it
is not a progress read at all; a 204 cannot distinguish accepted-and-discarded
from accepted-and-queued, so the acceptance read is the journal naming folders.

Also recorded the art door's wire shape, because guessing it reads as a server
bug: the list response carries the address in Url (there is no ImageUrl), the
Download POST takes camelCase type/imageUrl and answers 204, and PascalCase or an
empty imageUrl answers 500. RemoteImages is empty for items with no ProviderIds,
so art coverage is capped by identification, not by the image door.
dank added 1 commit 2026-11-02 03:00:42 -08:00
A per-episode RemoteImages query is never cached, so the same campaign that
dressed 620 series in an afternoon costs ~11 s per episode for one hit in five.
Across 42k episodes that is days of provider traffic to dress a fifth of them, and
clients show series and season art regardless. Recorded so nobody runs the pass on
a hunch; the door and the resumable log are the same if Frank orders it.
dank added 2 commits 2026-11-02 04:50:42 -08:00
The `Shows` flip landed today (one field, proven in the server's own
`LibraryOptions` answer), so the posture table no longer calls it a silent file
index and `Music`/`Collections` are named as the remaining ones.

Two corrections the same day's measurements force:

- The per-library `POST /Items/{id}/Refresh` answered `204` and queued nothing
  both before the flip and after it — zero journal lines for ten minutes, and
  `Scan Media Library` kept `LastTaskResult=None, EndTimeUtc=None`, the never-run
  signature. The provider switch was never the reason that door is dead; only
  `POST /Library/Refresh` runs (204, then `Running` inside 15 s at 33 % CPU), and
  it walks every library, so a library-scoped pass has no door on this build.
  That paragraph also records the null-pair read that separates "never ran" from
  "just finished", and that query+body on the same call answers `400` with an
  empty body whatever the values.
- The key law: a plugin owns its own config file under its own
  `<PluginConfiguration>` root and omits null settings, so TVDB's `<ApiKey>`
  element never existed and `install` refused — which is what `MISSING key` had
  been reporting since October. PR #121 fixes the writer.

The `Shows` art census replaces speculation with the split that matters: 2,908 of
3,558 series+season carry a primary, 624 have no provider id, 26 have an id and no
poster. A `Download` campaign over them took 20 s and downloaded nothing, which is
the measurement that says identification is the only lever left.
The existing rule says don't `pkill -f` a pattern your own command line carries,
and to prefer `pkill -x` — but a remote one-shot often has to name the pattern,
and hitting it again today cost a silent `ssh` exit 255 with zero output, which
reads like a network fault. Record the escape (`"serve[-]script.sh"`), the zsh
glob-expansion trap that makes an unquoted `[s]` a no-op, and the exit-code
signature.
dank merged commit 21c98bd485 into main 2026-11-02 07:16:53 -08:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dank/serverAppConfigs#120