From 1eb9c92d6a760bbd1d1be02715f8cdd646895adc Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 13 Sep 2026 00:12:56 -0400 Subject: [PATCH] fix(scanner): nil metadata_overrides crashed Reset to Scanned with SQLSTATE 23502 Reset to Scanned cleared the overrides row (successfully) but then set the in-memory copy to nil before handing it to updateMediaItem. pgx encodes a nil []string parameter as SQL NULL, so the follow-up UPDATE wrote metadata_overrides = NULL into the column's NOT NULL constraint and the whole rescan failed with: failed to update media item: ERROR: null value in column "metadata_overrides" of relation "media_items" violates not-null constraint (SQLSTATE 23502) Two changes: - RescanMediaItem's reset path assigns []string{} instead of nil, with a comment explaining the pgx nil-to-NULL encoding trap. - updateMediaItem routes the override set through utils.MergeOverrides, whose contract guarantees a non-nil slice, so no caller can write NULL into that column again (verified against pgx v5.9.2 source: a scanned '{}' round-trips as non-nil in both directions; the nil could only come from our own assignment). The plain Rescan path never hit this - only Reset did. Worse, the reset is the remedy when a book's cover_image_path override pins an empty cover, so the crash also blocked the way out of that state. After this fix, a plain rescan on an already-reset book repopulates scanned metadata and extracts the cover. --- internal/services/media_scanner.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/services/media_scanner.go b/internal/services/media_scanner.go index d3564b3..39a9fb3 100644 --- a/internal/services/media_scanner.go +++ b/internal/services/media_scanner.go @@ -2847,8 +2847,10 @@ func (s *MediaScanner) updateMediaItem(ctx context.Context, existing database.Me } // Keep user-customized fields, and keep the override set itself intact. + // MergeOverrides guarantees a non-nil slice: a nil []string would encode + // as SQL NULL and violate metadata_overrides' NOT NULL constraint. utils.ApplyMetadataOverrides(¶ms, existing) - params.MetadataOverrides = existing.MetadataOverrides + params.MetadataOverrides = utils.MergeOverrides(existing.MetadataOverrides) _, err = s.db.UpdateMediaItem(ctx, params) return err @@ -2869,7 +2871,10 @@ func (s *MediaScanner) RescanMediaItem(ctx context.Context, mediaItemID pgtype.U if err := s.db.ClearMediaItemMetadataOverrides(ctx, mediaItemID); err != nil { return fmt.Errorf("failed to clear metadata overrides: %w", err) } - item.MetadataOverrides = nil + // Empty - not nil: pgx encodes a nil []string parameter as SQL NULL, + // which would violate the column's NOT NULL constraint when + // updateMediaItem writes the row back. + item.MetadataOverrides = []string{} } folders, err := s.db.GetLibraryFolders(ctx, item.LibraryID)