Summary
Opening one specific movie in Jellyfin spins forever and never returns streams. The logs show InsertActionFilter.InsertMetaAsync retrying recursively, each attempt failing with:
SQLite Error 19: 'UNIQUE constraint failed: BaseItemProviders.ItemId, BaseItemProviders.ProviderId'
Because the insert never completes, execution never reaches the stream-resolution step (SyncStreams is never called for the item), so the client just buffers indefinitely.
The failure reproduces from a completely clean state — with the item's BaseItemProviders rows verified to be zero immediately before the insert — which means SaveItems is persisting a duplicate (ItemId, ProviderId) pair within a single insert batch, not colliding with pre-existing rows.
Environment
- Jellyfin: 10.11.9
- Gelato plugin: 0.26.15.1
- DB: SQLite (EF Core schema)
- Stream backend: AIOStreams → Comet (debrid). Backend confirmed healthy — see note below.
Affected item
- Title: Swapped (2026)
- IMDb:
tt29552248
- TMDB:
1007757
- A same–clean-name sibling already exists in the library: episode "We Swapped Places" (
tt21423786).
- A library search for the title returns 9 results, and
InsertActionFilter emits Media already exists; redirecting to canonical id <guid> before failing. The canonical id is deterministic — deleting the item and re-materializing regenerates the identical canonical GUID.
Steps to reproduce
- Library whose stream resolution is handled by Gelato (AIOStreams addon source).
- Use a title that (a) has a same–clean-name sibling already present in the library, and (b) triggers
InsertActionFilter: Media already exists; redirecting to canonical id ... on insert.
- Open the item from search.
Expected: item materializes, SyncStreams runs, streams are returned.
Actual: insert throws the UNIQUE constraint error on BaseItemProviders.(ItemId, ProviderId); InsertActionFilter.InsertMetaAsync retries recursively; SyncStreams never fires; the client spins.
Evidence this is a single-batch self-collision, not stale state
The conflicting provider rows were deleted and the table verified empty for the canonical id, Jellyfin was restarted, and the item was opened fresh:
-- before opening the item, against the live DB (read-only):
SELECT hex(ItemId), ProviderId, ProviderValue
FROM BaseItemProviders
WHERE ProviderValue IN ('tt29552248','1007757');
-- returns: (no rows)
The very next insert still throws UNIQUE constraint failed: BaseItemProviders.ItemId, BaseItemProviders.ProviderId. With nothing pre-existing to collide with, the only way to violate that constraint is for the insert batch to contain the same (ItemId, ProviderId) twice.
The failing EF command parameters confirm which key is duplicated — ProviderId size 4 ("Imdb"), value size 10 ("tt29552248"):
Failed executing DbCommand [Parameters=[@p0=? (DbType=Guid), @p1=? (Size=4), @p2=? (Size=10)]]
The input meta is well-formed (single IMDb id)
The addon-supplied meta has exactly one IMDb reference and one TMDB reference — no duplicate id binding — so the duplicate is generated downstream of the meta, inside Gelato:
{
"id": "tmdb:1007757",
"imdb_id": "tt29552248",
"name": "Swapped",
"links": [
{ "name": "7.3", "category": "imdb", "url": "https://imdb.com/title/tt29552248" }
// ...remaining links are share/genre/cast/director only; no second IMDb id binding
]
}
TMDB external_ids for 1007757 were also checked directly and contain a single, clean imdb_id: tt29552248.
Stack trace (trimmed)
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'UNIQUE constraint failed: BaseItemProviders.ItemId, BaseItemProviders.ProviderId'.
at Gelato.GelatoManager.SaveItems(IEnumerable`1 items, Folder parent)
at Gelato.GelatoManager.SaveItem(BaseItem item, Folder parent)
at Gelato.GelatoManager.InsertMeta(Folder parent, StremioMeta meta, User user, Boolean allowRemoteRefresh, Boolean refreshItem, Boolean queueRefreshItem, CancellationToken ct)
at Gelato.Filters.InsertActionFilter.<>c__DisplayClass10_0.<<InsertMetaAsync>b__0>d.MoveNext()
at Gelato.KeyLock.RunQueuedAsync(Guid key, Func`2 action, CancellationToken ct)
at Gelato.Filters.InsertActionFilter.InsertMetaAsync(Guid guid, Folder root, StremioMeta meta, User user)
at Gelato.Filters.InsertActionFilter.OnActionExecutionAsync(ActionExecutingContext ctx, ActionExecutionDelegate next)
The InsertMetaAsync → InsertMeta → SaveItems frames repeat on each retry (the loop).
Suspected cause
The failure is gated by the canonical-merge path: it only occurs on items that hit InsertActionFilter: Media already exists; redirecting to canonical id .... Ordinary titles (no redirect) insert and sync normally. This strongly suggests that when Gelato reconciles an incoming meta with an existing canonical item, it builds the provider-id collection by unioning/concatenating two provider sets without de-duplicating by ProviderId, then persists them in a single SaveItems batch — producing two (ItemId, "Imdb") rows and tripping the unique index.
The likely trigger for entering that merge path here is the same–clean-name sibling (tt21423786 "We Swapped Places") colliding with tt29552248 during dedup.
Notes
- This never hits Comet, so I can't even get any versions of the stream
- The backend is not implicated: the same IMDb id (
tt29552248) resolves and returns streams normally through the identical AIOStreams → Comet path when requested via Stremio, so content availability and id resolution are fine.
- DB-side cleanup (deleting the item row and orphaned provider rows) does not durably fix it, because re-materialization regenerates the same canonical id and re-triggers the duplicate insert.
Summary
Opening one specific movie in Jellyfin spins forever and never returns streams. The logs show
InsertActionFilter.InsertMetaAsyncretrying recursively, each attempt failing with:Because the insert never completes, execution never reaches the stream-resolution step (
SyncStreamsis never called for the item), so the client just buffers indefinitely.The failure reproduces from a completely clean state — with the item's
BaseItemProvidersrows verified to be zero immediately before the insert — which meansSaveItemsis persisting a duplicate(ItemId, ProviderId)pair within a single insert batch, not colliding with pre-existing rows.Environment
Affected item
tt295522481007757tt21423786).InsertActionFilteremitsMedia already exists; redirecting to canonical id <guid>before failing. The canonical id is deterministic — deleting the item and re-materializing regenerates the identical canonical GUID.Steps to reproduce
InsertActionFilter: Media already exists; redirecting to canonical id ...on insert.Expected: item materializes,
SyncStreamsruns, streams are returned.Actual: insert throws the
UNIQUE constrainterror onBaseItemProviders.(ItemId, ProviderId);InsertActionFilter.InsertMetaAsyncretries recursively;SyncStreamsnever fires; the client spins.Evidence this is a single-batch self-collision, not stale state
The conflicting provider rows were deleted and the table verified empty for the canonical id, Jellyfin was restarted, and the item was opened fresh:
The very next insert still throws
UNIQUE constraint failed: BaseItemProviders.ItemId, BaseItemProviders.ProviderId. With nothing pre-existing to collide with, the only way to violate that constraint is for the insert batch to contain the same(ItemId, ProviderId)twice.The failing EF command parameters confirm which key is duplicated —
ProviderIdsize 4 ("Imdb"), value size 10 ("tt29552248"):The input meta is well-formed (single IMDb id)
The addon-supplied meta has exactly one IMDb reference and one TMDB reference — no duplicate id binding — so the duplicate is generated downstream of the meta, inside Gelato:
{ "id": "tmdb:1007757", "imdb_id": "tt29552248", "name": "Swapped", "links": [ { "name": "7.3", "category": "imdb", "url": "https://imdb.com/title/tt29552248" } // ...remaining links are share/genre/cast/director only; no second IMDb id binding ] }TMDB
external_idsfor1007757were also checked directly and contain a single, cleanimdb_id: tt29552248.Stack trace (trimmed)
The
InsertMetaAsync → InsertMeta → SaveItemsframes repeat on each retry (the loop).Suspected cause
The failure is gated by the canonical-merge path: it only occurs on items that hit
InsertActionFilter: Media already exists; redirecting to canonical id .... Ordinary titles (no redirect) insert and sync normally. This strongly suggests that when Gelato reconciles an incoming meta with an existing canonical item, it builds the provider-id collection by unioning/concatenating two provider sets without de-duplicating byProviderId, then persists them in a singleSaveItemsbatch — producing two(ItemId, "Imdb")rows and tripping the unique index.The likely trigger for entering that merge path here is the same–clean-name sibling (
tt21423786"We Swapped Places") colliding withtt29552248during dedup.Notes
tt29552248) resolves and returns streams normally through the identical AIOStreams → Comet path when requested via Stremio, so content availability and id resolution are fine.