fix: a release change Lidarr applied but did not answer is recorded (M498)
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m43s
release / integration (push) Failing after 5m5s
release / android (push) Successful in 5m58s
release / Build signed APK (releases and dev) (push) Successful in 6m17s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped

On the first deploy pass Lidarr took longer than the client's 30s timeout to
answer the release change for Cracker Island: its album update unlinks the
files and queues the rescan before it replies. The change may well have
landed, but the resolver logged a failure and wrote no audit row, so the
album had no settle window and the report did not show it.

An unanswered PUT (ErrUnreachable) is now read back: when the album's
monitored release is the chosen one, the change is recorded as made. Any
other error, or a read-back that disagrees, stands as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 22:42:50 -04:00
co-authored by Claude Opus 5.5
parent e17556dce9
commit e1ad1c9ba2
2 changed files with 86 additions and 2 deletions
+24 -1
View File
@@ -668,12 +668,35 @@ func changeRelease(
return nil, "Lidarr's release of this album lists songs twice, and no other release lists each once while keeping what is on disk.", nil
}
if err := lid.SetMonitoredRelease(ctx, la.ID, best.ID); err != nil {
return nil, "", err
// Lidarr's album update unlinks the album's files and queues the rescan
// before it answers, and on a large album that outlasts the client's
// timeout while the change still lands (Cracker Island on the first
// deploy pass). An unanswered PUT is read back: when the release did
// change, it is recorded like any other, so the album gets its settle
// window and the report shows it.
if !errors.Is(err, lidarr.ErrUnreachable) || !releaseMonitored(ctx, lid, la.ID, best.ID) {
return nil, "", err
}
}
note := fmt.Sprintf("Lidarr now monitors the %s release, which lists each song once. The extra copies are merged once Lidarr has rescanned.", releaseLabel(best))
return &ReleaseChange{AlbumTitle: a.title, ArtistName: a.artist, From: from, To: best}, note, nil
}
// releaseMonitored reads whether the album's monitored release is now
// releaseID. A failed read answers false: the change is not assumed.
func releaseMonitored(ctx context.Context, lid LidarrLibrary, albumID, releaseID int) bool {
releases, err := lid.GetAlbumReleases(ctx, albumID)
if err != nil {
return false
}
for _, r := range releases {
if r.ID == releaseID {
return r.Monitored
}
}
return false
}
type releaseCandidate struct {
release lidarr.AlbumRelease
coverage int