Diagnostics

ASYNC_WATERFALL

2+/3+ sequential async flights, origin-proven (attribution enabled).


What you saw

  • Kind: perf
  • Severity: info (advisory, delivered on the diagnostics channel and never printed) or warn (code that works but is structurally wrong or expensive)

The console report opens with [ASYNC_WATERFALL] and the message, then an in line naming the owners that enclose the subject, root first, and an at line with the source location when the code was compiled with one. In a captured artifact the same finding is an event with code: "ASYNC_WATERFALL", ownerPath, and loc. Diagnostics explains each line.


Why it fires

Async flights ran in sequence when they might have run in parallel: each named flight provably could not start until the previous one resolved, and each took real time (the per-link durations are in the message/data).

Performance pathologies (from the attribution engine)

These fire only while attribution is enabled and describe cost, not incorrect behavior. The numbers in the message are measurements, not guesses.


How to fix it

Read the chain from attribution.history("waterfall") if you need more than the warning shows. (The server's <Loading> boundary counterpart is SSR_BOUNDARY_WATERFALL, below.) Repairs, in order of preference:

  1. If a later request does not need the earlier response, derive both from the same inputs so they start together (in one scope, read ALL async sources before using any — a not-ready throw at the first read stops the later ones from even starting).
  2. If the dependency is intrinsic, preload the dependent data (route preloaders, hover preloads) or join the requests server-side.
  3. False positive only if the work was ALREADY started by a layer the graph cannot see (a preloader or request cache handing out wrapper promises): that layer should call attribution.markFlight(promise, startedAt) (from solid-js/attribution) on what it hands out; the chain then breaks on origin proof. Depth-2 chains are info severity for exactly this reason — treat them as leads, not verdicts.

In tests

captureArtifact from @solidjs/diagnostics runs a scenario with the diagnostics channel open and returns the events it saw. expectNoDiagnostics(artifact) fails while ASYNC_WATERFALL is among them, with the events as evidence; after the fix, the code is gone from artifact.diagnostics. expectRerunBudget(artifact, max, { scope }) bounds how often a named scope may re-run, and expectNoWaste(artifact) fails on re-runs whose result was unchanged. Both read the attribution records, which captureArtifact collects unless attribution: false is passed. Check for regressions in tests shows the loop and the Vitest matcher form.


Learn more

  • Diagnostics: every code, with its kind, severity, and trigger.
  • Debugging reactivity: how to read a report and what each group of codes points at.
  • Attribution: the engine whose records explain a re-run, a hold, or a waterfall.
Last updated: 10/15/26, 10:07 PMEdit this pageReport an issue with this page