Youtarr v1.81.0, Jellyfin 10.11.11, one (admin) user, jellyfinWatchStatusAllUsers = true.
What happens
I have 219 videos in a Movies-type Jellyfin library, organised into 49 Collections, one per channel. The watch status sync reports success on every run and never writes a row:
Starting watch status sync {"trigger":"scheduled","videoCount":219,"serverCount":1}
Watch status sync completed for server {"serverType":"jellyfin","updated":0,"rowsWritten":0,"entries":124,"users":1}
video_watch_status stayed empty, so watched auto-removal never had a candidate to work with:
[Auto-Removal] Found watched videos eligible for removal {"count":0,"minDaysSinceWatched":0,"minVideoAgeDays":0}
Nothing logs an error. The only clue is rowsWritten: 0, and that looks identical to a run which genuinely had nothing to do.
The 124 entries in that log line aren't videos at all. In my case they were 50 BoxSets plus 74 Episodes from unrelated libraries. None of the 219 videos came back in the response.
Why
fetchWatchStates in server/modules/mediaServers/adapters/jellyfinAdapter.js doesn't pass collapseBoxSetItems:
const params = {
userId: user.id,
includeItemTypes: 'Video,Movie,Episode',
recursive: true,
fields: 'Path',
enableUserData: true,
};
Jellyfin's ItemsController takes that parameter as a nullable bool and hands it to the query without supplying a default. When it arrives null, Folder.cs decides for itself:
var param = query.CollapseBoxSetItems;
if (param.HasValue) { return param.Value && AllowBoxSetCollapsing(query); }
var config = configurationManager.Configuration;
bool queryHasMovies = query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Movie);
bool queryHasSeries = query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Series);
bool collapseMovies = config.EnableGroupingMoviesIntoCollections;
bool collapseSeries = config.EnableGroupingShowsIntoCollections;
if (user is not null)
{
bool canCollapse = (queryHasMovies && collapseMovies) || (queryHasSeries && collapseSeries);
return canCollapse && AllowBoxSetCollapsing(query);
}
So on a user-scoped query that includes Movie, Jellyfin swaps out every movie belonging to a Collection and returns the Collection instead. EnableGroupingMoviesIntoCollections is a server-wide setting (Dashboard, Libraries, Display, "Group movies into collections") and it ships enabled, so this should hit anyone who has grouped their library into Collections.
Reproducing it
- Point Youtarr at a Movies-type Jellyfin library.
- Put the videos into one or more Collections.
- Leave "Group movies into collections" checked, which is the default.
- Run a watch status sync.
Here's the same query with and without the parameter, against my library:
| Query |
Result |
/Items?UserId=…&ParentId=<lib>&IncludeItemTypes=Movie&Recursive=true |
49 items, all BoxSet, Path is …/collections/<name> [boxset] |
same, plus &CollapseBoxSetItems=false |
219 items, all Movie, real file paths, 12 with UserData.Played = true |
Suggested fix
Add the parameter in fetchWatchStates:
const params = {
userId: user.id,
includeItemTypes: 'Video,Movie,Episode',
recursive: true,
fields: 'Path',
enableUserData: true,
collapseBoxSetItems: false,
};
Collapsing is a browse-view convenience and there's no case where you'd want it on a data query, so I think the same applies to the /Items call used for file resolution further up the adapter.
Separately: it might be worth logging a warning when a sync matches none of N videos and N is greater than zero. That case currently looks exactly like a healthy no-op, which is why it took me a couple of days to spot.
Workaround
Unchecking "Group movies into collections" fixes it right away. My next sync wrote all 219 rows. It's a server-wide setting though, so it also changes how ordinary movie libraries display.
Youtarr v1.81.0, Jellyfin 10.11.11, one (admin) user,
jellyfinWatchStatusAllUsers = true.What happens
I have 219 videos in a Movies-type Jellyfin library, organised into 49 Collections, one per channel. The watch status sync reports success on every run and never writes a row:
video_watch_statusstayed empty, so watched auto-removal never had a candidate to work with:Nothing logs an error. The only clue is
rowsWritten: 0, and that looks identical to a run which genuinely had nothing to do.The 124 entries in that log line aren't videos at all. In my case they were 50 BoxSets plus 74 Episodes from unrelated libraries. None of the 219 videos came back in the response.
Why
fetchWatchStatesinserver/modules/mediaServers/adapters/jellyfinAdapter.jsdoesn't passcollapseBoxSetItems:Jellyfin's
ItemsControllertakes that parameter as a nullable bool and hands it to the query without supplying a default. When it arrives null,Folder.csdecides for itself:So on a user-scoped query that includes
Movie, Jellyfin swaps out every movie belonging to a Collection and returns the Collection instead.EnableGroupingMoviesIntoCollectionsis a server-wide setting (Dashboard, Libraries, Display, "Group movies into collections") and it ships enabled, so this should hit anyone who has grouped their library into Collections.Reproducing it
Here's the same query with and without the parameter, against my library:
/Items?UserId=…&ParentId=<lib>&IncludeItemTypes=Movie&Recursive=trueBoxSet,Pathis…/collections/<name> [boxset]&CollapseBoxSetItems=falseMovie, real file paths, 12 withUserData.Played = trueSuggested fix
Add the parameter in
fetchWatchStates:Collapsing is a browse-view convenience and there's no case where you'd want it on a data query, so I think the same applies to the
/Itemscall used for file resolution further up the adapter.Separately: it might be worth logging a warning when a sync matches none of N videos and N is greater than zero. That case currently looks exactly like a healthy no-op, which is why it took me a couple of days to spot.
Workaround
Unchecking "Group movies into collections" fixes it right away. My next sync wrote all 219 rows. It's a server-wide setting though, so it also changes how ordinary movie libraries display.