From e47fb6083be589001c109c904169879596842e21 Mon Sep 17 00:00:00 2001 From: Major Date: Mon, 13 Jul 2026 11:01:42 +0200 Subject: [PATCH 1/2] fix: stop noisy false-error logs during full sync deleteAssetsNotIn deferred its temp-table DROP until after tx.Commit(), so the drop always ran on an already-closed transaction and logged "[DB] Failed to drop temp table: sql: transaction has already been committed or rolled back" on every successful full sync. Move the DROP inside the transaction, before Commit; error paths still roll back via defer. doUserFullSync logged "Library sync failed during full sync" for the expected 401/403 a non-admin key gets on /api/libraries, duplicating the accurate warning syncLibraries already emits. Suppress that extra line for the expected auth case; genuine (non-HTTP) failures still log. --- backend/database.go | 11 ++++++----- backend/syncService.go | 8 +++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/database.go b/backend/database.go index 0b65fe7..66c0aba 100644 --- a/backend/database.go +++ b/backend/database.go @@ -860,16 +860,17 @@ func (d *Database) deleteAssetsNotIn(ctx context.Context, userID string, assetID if err := bulkInsertTemp(ctx, tx, "tmpKeepAssets", assetIDs); err != nil { return fmt.Errorf("populate temp table: %w", err) } - defer func() { - if _, err := tx.ExecContext(ctx, "DROP TABLE IF EXISTS tmpKeepAssets"); err != nil { - log.Printf("[DB] Failed to drop temp table: %v", err) - } - }() if _, err := tx.ExecContext(ctx, "DELETE FROM assets WHERE userID = ? AND immichID NOT IN (SELECT val FROM tmpKeepAssets)", userID); err != nil { return fmt.Errorf("delete stale assets: %w", err) } + // Drop inside the transaction: a deferred drop would run after Commit, when the + // tx is already closed. Error paths roll the whole tx back via defer tx.Rollback(). + if _, err := tx.ExecContext(ctx, "DROP TABLE IF EXISTS tmpKeepAssets"); err != nil { + return fmt.Errorf("drop temp table: %w", err) + } + return tx.Commit() } diff --git a/backend/syncService.go b/backend/syncService.go index 2c2223e..84c1533 100644 --- a/backend/syncService.go +++ b/backend/syncService.go @@ -217,7 +217,13 @@ func (s *SyncService) doUserFullSync(ctx context.Context, userID string, immich s.syncStacks(ctx, userID, immich) if err := s.syncLibraries(ctx, userID, immich); err != nil { - log.Printf("[Sync] Library sync failed during full sync for user %s: %v", userID, err) + // A 401/403 only means the key is not an admin key: syncLibraries already logs + // that as an expected condition, so don't repeat it here as a failure. + var httpErr *ImmichHTTPError + isMissingAccess := errors.As(err, &httpErr) && (httpErr.StatusCode == http.StatusUnauthorized || httpErr.StatusCode == http.StatusForbidden) + if !isMissingAccess { + log.Printf("[Sync] Library sync failed during full sync for user %s: %v", userID, err) + } s.db.deleteSyncState(ctx, userID, "libraryIDBackfillDone") } else { if err := s.db.setSyncState(ctx, userID, "libraryIDBackfillDone", "true"); err != nil { From b3d5fcfa376de943558a17cb7e3480b42686258b Mon Sep 17 00:00:00 2001 From: Major Date: Mon, 13 Jul 2026 11:21:32 +0200 Subject: [PATCH 2/2] review: make temp-table drop best-effort like sibling helpers deleteAssetsNotIn returned an error on DROP failure, diverging from deleteAlbumsNotIn/deleteTagsNotIn which drop inline before Commit and ignore the result. Aborting the tx there could turn a successful asset cleanup into a caller error. Match the established best-effort pattern; the drop still runs inside the transaction, so the original post-Commit failure is still fixed. --- backend/database.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/backend/database.go b/backend/database.go index 66c0aba..dfe25e9 100644 --- a/backend/database.go +++ b/backend/database.go @@ -865,12 +865,7 @@ func (d *Database) deleteAssetsNotIn(ctx context.Context, userID string, assetID return fmt.Errorf("delete stale assets: %w", err) } - // Drop inside the transaction: a deferred drop would run after Commit, when the - // tx is already closed. Error paths roll the whole tx back via defer tx.Rollback(). - if _, err := tx.ExecContext(ctx, "DROP TABLE IF EXISTS tmpKeepAssets"); err != nil { - return fmt.Errorf("drop temp table: %w", err) - } - + tx.ExecContext(ctx, "DROP TABLE IF EXISTS tmpKeepAssets") return tx.Commit() }