diff --git a/CHANGELOG.md b/CHANGELOG.md index ee3faca4e..47f144140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed +- **Cover-image lookup no longer 500s when one upstream metadata source returns a partial envelope:** `ImagesController.GetImage`'s fallback metadata-driven cover path accesses `env.metadata` on `GetMetadataAsync`'s `object?` return via the C# `dynamic` binder. When the returned envelope shape lacks a `metadata` property — which happens when one of the upstream sources (e.g., Audnexus) 500s and the service still returns a partial envelope — the binder throws `RuntimeBinderException`. That exception wasn't in `IsRecoverableImageLookupException`'s whitelist, so it bypassed the inner catch and the entire image endpoint returned 500. Adding `RuntimeBinderException` to the recoverable list lets the inner catch swallow the bad envelope and the lookup falls through to the next candidate URL or a placeholder, matching how the other recoverable failures behave. + ## [0.2.71] - 2026-04-17 ### Added diff --git a/listenarr.api/Controllers/ImagesController.cs b/listenarr.api/Controllers/ImagesController.cs index 2aa62fffa..419e69290 100644 --- a/listenarr.api/Controllers/ImagesController.cs +++ b/listenarr.api/Controllers/ImagesController.cs @@ -1040,7 +1040,16 @@ or ArgumentException or FormatException or UriFormatException or System.Net.Http.HttpRequestException - or System.Text.Json.JsonException; + or System.Text.Json.JsonException + // GetMetadataAsync returns object?, and the fallback metadata path + // accesses fields via dynamic. When the returned envelope shape + // lacks the expected `metadata` property (e.g., when one of the + // upstream sources like Audnexus 500s and the service still + // returns a partial envelope), the C# dynamic binder throws + // RuntimeBinderException. Without this in the recoverable filter, + // a single bad envelope shape bubbles up to the outer catch and + // turns the whole image lookup into a 500. + or Microsoft.CSharp.RuntimeBinder.RuntimeBinderException; } private static string ResolvePathWithOptionalBase(string? basePath, string candidatePath)