_download_resource uses the literal string "ERROR" as a failure sentinel in
_RESOURCE_CACHE — written on SSRF rejection (download.py:66) and on any exception at
all via a bare except Exception (download.py:81), which includes a transient DNS hiccup,
a timeout, or a one-off 5xx. Three consumers branch on that sentinel, and one of them is
wrong (langgraph-agent-convention):
get_resource returns _RESOURCE_CACHE.get(url, "") (download.py:24). Only "" — the
never-fetched case — is falsy. "ERROR" is a non-empty string and therefore , so
is and the URL is never re-added to .
Why this is the silent kind
Nothing raises. The user-visible sequence is: the resource card stays on the canvas, the
progress log for that URL is marked done (download.py:109 flips done for every entry it
queued — and after the first failure it queues nothing at all, so the log stays clean), the
model never receives the content, and no error reaches state or the UI. Two downstream
symptoms a dev will actually report:
- "my resource has no content" —
chat_nodesilently drops it fromresources. - "fact-check says nothing supports this claim" —
fact_check_nodeskips the same resource, so a genuinely-supported claim comes backsupported: falsewith an empty . This is a failure in the report itself, not merely a missing fetch.
The only recovery today is restarting the process (the cache is module-level and dies with it).
The fix, and the trap in it
Change the test, not just the sentinel: if get_resource(resource["url"]) in ("", "ERROR").
Swapping "ERROR" for None without touching download.py:97 would work too, but then the
two consumers (, ) both go stale and start feeding
the model the literal text of the sentinel — change all four sites together or change only the
truthiness test. Note also that any future eviction policy for the cache
(see ) must treat a miss as "re-download", never as "unsupported
claim" — the same asymmetry, one layer down.
Related but distinct: resource-cache-unbounded (P3) tracks the cache's unbounded growth.
This row is about permanent failure caching — different defect, same dict.