Skip to content

fix(images): swallow RuntimeBinderException in cover-lookup fallback#600

Draft
kevinheneveld wants to merge 1 commit into
Listenarrs:canaryfrom
kevinheneveld:fix/images-recoverable-runtime-binder
Draft

fix(images): swallow RuntimeBinderException in cover-lookup fallback#600
kevinheneveld wants to merge 1 commit into
Listenarrs:canaryfrom
kevinheneveld:fix/images-recoverable-runtime-binder

Conversation

@kevinheneveld
Copy link
Copy Markdown

Summary

ImagesController.GetImage's fallback metadata-driven cover path accesses fields via dynamic on the object? returned by GetMetadataAsync. When the returned envelope shape lacks the expected metadata property — which happens when one of the upstream sources (e.g., Audnexus) 500s and the service still returns a partial envelope — the C# dynamic binder throws RuntimeBinderException.

That exception type isn't in IsRecoverableImageLookupException's whitelist, so it bypasses the surrounding catch when filter and bubbles to the outer handler. The whole image endpoint then returns 500 instead of falling through to the next candidate URL (OpenLibrary ISBN cover, etc.) or the placeholder.

Repro

In live logs after a metadata-backfill modal search, six candidates returned 500 from /api/v1/images/{asin} for ASINs B0DPMHF5HN, B009S8FKUU, B0H23C15LQ, B002VEU2PQ, B0DPMFLQPH, B0DPMCLFLD. Each is preceded by [WRN] [Listenarr.Application.Metadata.AudnexusService] Audnexus API returned status code InternalServerError for ASIN ... and [INF] Successfully fetched metadata from Audible for ASIN: ... — the Audible response succeeds, but the envelope shape doesn't have a metadata member. Each then logs:

[ERR] [Listenarr.Api.Controllers.ImagesController] Error retrieving image for identifier: <ASIN>
   at Listenarr.Api.Controllers.ImagesController.GetImage(String identifier) in /src/listenarr.api/Controllers/ImagesController.cs:line 610

Line 610 is object? mdObj = env.metadata; — the dynamic access. UI-side: those candidates render with the browser's broken-image icon in the metadata-backfill modal candidate list.

Fix

Add Microsoft.CSharp.RuntimeBinder.RuntimeBinderException to IsRecoverableImageLookupException. The dynamic access stays — this just makes its failure mode match the existing "log debug and continue" pattern used for IOException / JsonException / HttpRequestException etc. The lookup falls through to the next candidate URL or the placeholder, same as any other recoverable failure.

A larger refactor that replaces the dynamic access with a strongly-typed shape would be cleaner but is out of scope for this defensive patch.

Test plan

  • Backend builds clean
  • After deploy, re-trigger the failing scenario from the live log and confirm: no 500 from /api/v1/images/{asin}, log shows [DBG] Failed to parse fallback metadata envelope for {Identifier} and the lookup falls through.

🤖 Generated with Claude Code

@kevinheneveld kevinheneveld requested a review from a team May 18, 2026 00:34
kevinheneveld pushed a commit to kevinheneveld/Listenarr that referenced this pull request May 18, 2026
…rafting

State on top of the post-rebase baseline:
  - 5 more commits on kevin/live (preview button, hydrated audiobook,
    publish-date normalize, image-500 fix, modal z-index prop)
  - PR Listenarrs#600 + Listenarrs#603 opened (defensive fixes, non-draft)
  - PR Listenarrs#604 + Listenarrs#605 opened as drafts (wave 1 of the staggered
    feature-PR queue per Kevin's pacing instruction)
  - Issue #5 filed for LibriVox metadata source (deferred)
  - 8 features on kevin/live still without an upstream PR — queued
    with a per-day schedule

Live image: listenarr:local-20260517-1651 (head a712b49).
Two-step rollback: 1634 → 1617.
@kevinheneveld kevinheneveld marked this pull request as draft May 18, 2026 15:45
kevinheneveld pushed a commit to kevinheneveld/Listenarr that referenced this pull request May 18, 2026
- Live image bumped to listenarr:local-20260518-0750 (head 40a436b).
- PR Listenarrs#580 rewritten on review feedback from T4g1: NzbgetSafeRedirectHandler
  replaces URL-embedded creds, covers both /xmlrpc and /jsonrpc.
- All 9 open upstream PRs are now draft (converted Listenarrs#580, Listenarrs#600, Listenarrs#603).
- kevin/live and kevin/live-rebased both carry the new commit.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
kevinheneveld pushed a commit to kevinheneveld/Listenarr that referenced this pull request May 18, 2026
- Live image bumped to listenarr:local-20260518-0750 (head 40a436b).
- PR Listenarrs#580 rewritten on review feedback from T4g1: NzbgetSafeRedirectHandler
  replaces URL-embedded creds, covers both /xmlrpc and /jsonrpc.
- All 9 open upstream PRs are now draft (converted Listenarrs#580, Listenarrs#600, Listenarrs#603).
- kevin/live and kevin/live-rebased both carry the new commit.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
ImagesController.GetImage's fallback metadata-driven cover path
accesses fields via dynamic on the object? returned by
GetMetadataAsync. When the returned envelope shape lacks the expected
"metadata" property — which happens when one of the upstream sources
(e.g., Audnexus) 500s and the service still returns a partial
envelope — the C# dynamic binder throws RuntimeBinderException.

That exception type wasn't in IsRecoverableImageLookupException's
whitelist, so it bypassed the surrounding catch-when filter and
bubbled to the outer handler. The whole image endpoint then returned
500 instead of falling through to the next candidate URL (OpenLibrary
ISBN cover, etc.) or the placeholder.

Add RuntimeBinderException to the recoverable list. The dynamic
access stays — this just makes its failure mode match the existing
"log debug and continue" pattern used for IOException / JsonException
/ HttpRequestException etc.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@kevinheneveld kevinheneveld force-pushed the fix/images-recoverable-runtime-binder branch from c513aac to c459668 Compare May 19, 2026 16:13
kevinheneveld pushed a commit to kevinheneveld/Listenarr that referenced this pull request May 19, 2026
…rafting

State on top of the post-rebase baseline:
  - 5 more commits on kevin/live (preview button, hydrated audiobook,
    publish-date normalize, image-500 fix, modal z-index prop)
  - PR Listenarrs#600 + Listenarrs#603 opened (defensive fixes, non-draft)
  - PR Listenarrs#604 + Listenarrs#605 opened as drafts (wave 1 of the staggered
    feature-PR queue per Kevin's pacing instruction)
  - Issue #5 filed for LibriVox metadata source (deferred)
  - 8 features on kevin/live still without an upstream PR — queued
    with a per-day schedule

Live image: listenarr:local-20260517-1651 (head a712b49).
Two-step rollback: 1634 → 1617.
kevinheneveld pushed a commit to kevinheneveld/Listenarr that referenced this pull request May 19, 2026
- Live image bumped to listenarr:local-20260518-0750 (head 40a436b).
- PR Listenarrs#580 rewritten on review feedback from T4g1: NzbgetSafeRedirectHandler
  replaces URL-embedded creds, covers both /xmlrpc and /jsonrpc.
- All 9 open upstream PRs are now draft (converted Listenarrs#580, Listenarrs#600, Listenarrs#603).
- kevin/live and kevin/live-rebased both carry the new commit.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants