Skip to content

Retry transient NFT download failures instead of persisting them for good - #3060

Open
jlobue10 wants to merge 1 commit into
Chia-Network:release/2.7.4from
jlobue10:fix/nft-transient-cache-errors
Open

Retry transient NFT download failures instead of persisting them for good#3060
jlobue10 wants to merge 1 commit into
Chia-Network:release/2.7.4from
jlobue10:fix/nft-transient-cache-errors

Conversation

@jlobue10

@jlobue10 jlobue10 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #3058

Problem

The NFT media cache persists every failed download as an ERROR sidecar and, apart from timeouts (retried once per session) and aborts, never fetches that URL again. That is the right call for a resource that is gone (404), but two conditions that clear on their own were being recorded the same way, and each of them leaves the NFT on "Preview is not available" in every later session until the whole cache is cleared:

  1. HTTP error: 504nftstorage.link was down for weeks and answered 504 for everything. It is back now as a redirector to ipfs.io (the reporter's URL resolves and hashes correctly), but any GUI that recorded the 504 keeps throwing it from the sidecar without ever re-requesting the file. That is why the reporter sees cacheAPI:getChecksum: HTTP error: 504 for a URL that curl fetches in under a second.
  2. net::ERR_BLOCKED_BY_RESPONSEipfs.io (which nftstorage.link now redirects to, and which the ipfs:// gateway option uses) sits behind Cloudflare bot management and intermittently answers Electron's net.request with a 403 "Just a moment..." challenge page (cf-mitigated: challenge). The GUI cannot solve a JS challenge, so the file fails, and the very next request usually succeeds. That page carries Cross-Origin-Resource-Policy: same-origin, which Chromium turns into ERR_BLOCKED_BY_RESPONSE when the request has an initiator and into a plain HTTP 403 otherwise; both were persisted as permanent failures.

I could not confirm the race in NFTPreview suggested in the issue: getURI and getChecksum share the same fetchRemoteContent promise for a URL (ongoingRequests), so the optimistic tile waits for the same download the verifier runs and cannot observe a different outcome for the same URI.

Fix

isTransientDownloadError classifies timeouts, HTTP 5xx, 403/408/425/429 and net::ERR_* failures as transient. CacheManager retries a persisted transient failure once per session (as it already did for timeouts) and again whenever TRANSIENT_ERROR_RETRY_DELAY (10 minutes) has elapsed since the failure was recorded, so a gateway hiccup no longer blanks an NFT until the GUI restarts. Missing resources (404/410) and the size cap keep their settled behaviour; the IpfsGatewayDisabledError path is untouched (it was never persisted).

The retry decision uses the sidecar's existing timestamp, so already-poisoned caches recover on their own after upgrading: the next visit to the NFT re-requests the file.

Verification

  • Reproduced the gateway behaviour with a standalone Electron 39 and Electron 43.4.0 (the version 2.7.4 ships) probe using the reporter's URLs: the redirect from nftstorage.link to ipfs.io is followed fine, but ipfs.io answered a burst of requests with the Cloudflare challenge (HTTP 403, cf-mitigated: challenge, CORP same-origin) before letting the same requests through minutes later. A local server returning the challenge page's headers reproduces net::ERR_BLOCKED_BY_RESPONSE on both Electron versions whenever the request carries an initiator.
  • Local cache on this machine: 445 sidecars still hold HTTP error: 504 for nftstorage.link URLs recorded while the host was down, plus 12 × HTTP error: 403 from the same host.
  • New unit tests: the classifier table, no retry within the delay, retry in a later session for 504 / 403 / ERR_BLOCKED_BY_RESPONSE, retry within the session once the delay has elapsed, and a 404 staying settled across sessions.
  • packages/gui jest: 396/396. eslint and prettier clean; no new tsc errors in the touched files.

🤖 Generated with Claude Code


Note

Low Risk
Changes are confined to Electron NFT download caching and retry policy; permanent-error and IPFS-gateway-disabled paths are unchanged.

Overview
NFT media cache errors from gateway outages, Cloudflare challenges, rate limits, and similar transient failures no longer stay poisoned until the user clears the whole cache.

isTransientDownloadError (in downloadFile) now classifies timeouts, HTTP 5xx, selected 4xx (403/408/425/429), and net::ERR_* as retriable; 404/410 and size-cap errors remain permanent. CacheManager uses that helper instead of timeout-only checks, tracks session failures in transientFailureUrls, and exposes a 10-minute TRANSIENT_ERROR_RETRY_DELAY: persisted transient ERROR sidecars are retried once per new session and again in-session once the sidecar timestamp is older than the delay. Immediate repeat fetches in the same session still do not hammer the host.

Unit tests cover the classifier, no immediate retry for a fresh 504, cross-session retry for 504/403/ERR_BLOCKED_BY_RESPONSE, in-session retry after the delay, and 404 staying settled.

Reviewed by Cursor Bugbot for commit 3e4b097. Bugbot is set up for automated code reviews on this repo. Configure here.

@danieljperry

Copy link
Copy Markdown
Contributor

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit a3b8bcc. Configure here.

@emlowe

emlowe commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

ckise and reooen for base branch change

@emlowe emlowe closed this Sep 3, 2026
@emlowe emlowe reopened this Sep 3, 2026
@emlowe
emlowe changed the base branch from release/2.7.4 to main September 3, 2026 15:25
@emlowe

emlowe commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@jlobue10 can you retarget this to release/2.7.4 branch - I can't do it the easy way here through the PR as it pulls in a bunch of unrelated localization changes.

@jlobue10

jlobue10 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

@jlobue10 can you retarget this to release/2.7.4 branch - I can't do it the easy way here through the PR as it pulls in a bunch of unrelated localization changes.

Will do

…good

The media cache persists every failed download as an ERROR sidecar and,
apart from timeouts (retried once per session) and aborts, never fetches
that URL again. Gateway conditions that clear on their own therefore
poison an NFT until the whole cache is cleared: an HTTP 504 recorded while
nftstorage.link was down stays a 504 after the host came back (it now
redirects to ipfs.io), and the Cloudflare bot challenge that ipfs.io
intermittently answers with — an HTTP 403 "Just a moment..." page, or
net::ERR_BLOCKED_BY_RESPONSE when Chromium rejects that page's
Cross-Origin-Resource-Policy header — is recorded as a permanent failure
although the very next request usually succeeds. The NFT then shows
"Preview is not available" in every later session (Chia-Network#3058).

Classify timeouts, HTTP 5xx, 403/408/425/429 and net::ERR_* failures as
transient. A persisted transient failure is retried once per session, as
timeouts already were, and again whenever the retry delay (10 minutes)
has elapsed since it was recorded, so a gateway hiccup no longer blanks
an NFT until the GUI restarts. Missing resources (404/410) and the size
cap keep their settled behaviour.

Fixes Chia-Network#3058

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ni9VuLznDQTZh2V8aBis76
jlobue10 added a commit to jlobue10/chia-blockchain-gui that referenced this pull request Sep 3, 2026
…ough

The ipfs:// gateway option always used the public ipfs.io gateway. That
gateway (and nftstorage.link, which now redirects to it) sits behind
Cloudflare bot management and intermittently answers the GUI's requests
with a 403 challenge page it cannot solve, while other public gateways and
a local IPFS node serve the same files without trouble (Chia-Network#3058). Users had
no way to route around it.

Add an "IPFS gateway" preference under the gateway option. Whatever the
user enters is normalized to a path-gateway base (`https://dweb.link`,
`https://dweb.link/ipfs` and `https://dweb.link/ipfs/` all become
`https://dweb.link/ipfs/`); https is required except for a plain-http
gateway on this machine, which is how a local Kubo node serves by default.
Anything unusable falls back to the default rather than producing URLs
that cannot be fetched. The main process reads the persisted value at
every network call site, as it already does for the option itself.

Changing the gateway takes effect immediately: hash verification and
failed metadata fetches re-run, and the cache re-requests an ipfs
failure recorded under a different gateway right away instead of waiting
out the transient-error delay — the sidecar now records which gateway a
failed ipfs request went through. The dapp dialog's oversized-image
fallback skips a plain-http gateway URL, which its CSP could not embed.

Stacked on Chia-Network#3060, which introduces the retry delay this builds on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ni9VuLznDQTZh2V8aBis76
@jlobue10
jlobue10 force-pushed the fix/nft-transient-cache-errors branch from a3b8bcc to 3e4b097 Compare September 3, 2026 15:46
@jlobue10
jlobue10 changed the base branch from main to release/2.7.4 September 3, 2026 15:46
@emlowe

emlowe commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

bugbot review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3e4b097. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Some IPFS NFT previews don't load

4 participants