antiwork/gumroad · error · ActiveStorage::FileNotFoundError
We couldn't download that file, please check the URL and try
Error message
We couldn't download that file, please check the URL and try again.
What it means
Raised as ActiveStorage::FileNotFoundError at line 201 when SsrfFilter.get completes but the response is not a Net::HTTPSuccess, and mapped to this message; #process's rescue also maps INTERNET_EXCEPTIONS (SocketError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH, ...) to the same string. It means the URL was structurally valid and SSRF-safe but the fetch itself failed: a 4xx/5xx status or a transport-level error. (Note: an unresolvable hostname maps to the 'valid public URL' message instead, via SsrfFilter::UnresolvedHostname.)
Source
Thrown at app/services/create_public_media_service.rb:201
raise URI::InvalidURIError, "URL '#{normalized_url}' is not a web url" unless uri.scheme.in?(%w[http https])
raise URI::InvalidURIError, "URL must include a valid host" if uri.host.blank?
tempfile = Tempfile.new(binmode: true)
begin
response = SsrfFilter.get(normalized_url) do |http_response|
raise RemoteFileTooLarge if http_response["content-length"].to_i > MAX_IMAGE_BYTES
write_file = http_response.is_a?(Net::HTTPSuccess)
received_bytes = 0
byte_limit = MAX_IMAGE_BYTES
http_response.read_body do |chunk|
received_bytes += chunk.bytesize
raise RemoteFileTooLarge if received_bytes > byte_limit
tempfile.write(chunk) if write_file
end
end
raise ActiveStorage::FileNotFoundError unless response.is_a?(Net::HTTPSuccess)
tempfile.rewind
# Sniff the real content type from the file bytes. The remote server's header is used only
# as a hint — a mislabeled or disguised file is classified by what it actually contains.
content_type = Marcel::MimeType.for(tempfile, name: filename_from(uri), declared_type: response.content_type)
tempfile.rewind
ActiveStorage::Blob.create_and_upload!(
io: tempfile,
filename: filename_with_extension(filename_from(uri), content_type),
content_type:,
)
ensure
tempfile.close!
end
end
def normalize_url(raw)
value = raw.to_sView on GitHub (pinned to afeacbd394)
Solutions
- Open the URL in an incognito window to confirm it is publicly reachable without cookies
- Use a stable permalink (your own hosting) rather than expiring share links
- Retry after a short wait if the failure looked like a transient 5xx or connection reset
- Direct-upload the file (signed_blob_id) so no remote fetch is needed at all
Example fix
# before url: 'https://s3.amazonaws.com/bucket/logo?X-Amz-Expires=60' # link expired -> 403 # => failure: We couldn't download that file... # after url: 'https://seller-site.com/assets/logo.png' # permanent, public
Defensive patterns
Strategy: retry
Validate before calling
require 'net/http'
uri = URI(url)
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: 5) do |http|
code = http.head(uri.request_uri).code
raise "endpoint returns #{code}" unless code.start_with?('2')
end Try / catch
result = nil begin attempts = (attempts || 0) + 1 result = CreatePublicMediaService.new(seller:, url:).process # HTTP 5xx / connection resets land here as failure Result, not exceptions: # retry the whole process call with backoff while attempts < 3 end while !result.success? && result.error_message.include?(%q[couldn't download]) && attempts < 3
Prevention
- Pre-flight the URL with a HEAD request from the same environment before submitting it
- Host files at stable public permalinks rather than expiring presigned URLs or hotlink-protected origins
- Distinguish failure modes by message only as a last resort — better to fix the URL than to classify the error string
When it happens
Trigger: The remote server returns 404 (file moved/deleted), 403 (hotlink protection or an expired presigned URL), or 5xx; or the TCP connection is refused/reset mid-fetch. URL passed all scheme/host/SSRF checks, so the failure is downstream of them.
Common situations: Expired S3/Google-Drive presigned links, hosts that block non-browser user-agents or referers, origin outages behind a CDN, transient 502/503s.
Related errors
- That file is too large. Images can be up to #{MAX_IMAGE_BYTE
- Message is required
- You can attach up to #{DisputeEvidence::MAX_CUSTOMER_COMMUNI
- One of the uploaded files is not a JPG, PNG, or PDF.
- One of the uploaded files exceeds the maximum size allowed.
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/dc5b69adf25e8240.
Report an issue: GitHub.