Diagnostics
A diagnostic is a finding the runtime makes about your code while it runs: a reactive value read where nothing tracks it, a write from inside a computation, an effect that re-runs because of its own write, a wait the screen never acknowledged. Each finding carries a stable code, and every code has a page here that says what you saw, why it fires, and how to fix it.
A development build prints each finding to the console.
An observe build delivers the same findings as events on OBSERVE.diagnostics without printing them, and a test captures them with captureArtifact from @solidjs/diagnostics so that expectNoDiagnostics(artifact) fails on a regression.
A production build reports nothing.
Severity says what kind of finding it is: error is broken behavior, warn is code that works but is structurally wrong or expensive, and info is advisory, delivered on the diagnostics channel and never printed.
Some codes fire only while the attribution engine is enabled, because they measure cost or waiting rather than correctness; their pages say so.
Read a report
One console entry per finding:
[STRICT_READ_UNTRACKED] Reactive value "title" read directly in <TodoRow> will not update. Move it into a tracking scope (JSX, a memo, or an effect's compute function). in <App> › <TodoList> › <TodoRow> at http://localhost:5173/src/components/TodoRow.tsx:18:3[STRICT_READ_UNTRACKED] what it means and how to fix it: https://v2.solidjs.com/reference/solid-js/diagnostics/strict-read-untracked[STRICT_READ_UNTRACKED] agent repair guide: node_modules/solid-js/skills/reactivity-diagnostics/SKILL.md#strict_read_untracked — https://github.com/solidjs/solid/blob/next/packages/solid/skills/reactivity-diagnostics/SKILL.md#strict_read_untracked- The first line is the code in brackets and the message: what the runtime observed and, for most codes, what to change.
- The
inline is the chain of owners from the root down to the scope that produced the finding: components as<Name>, computations by thenameoption you gave them oreffect/computedby default. Noinline means the subject has no named owner: a top-level scope, or an unowned primitive, which for theNO_OWNER_*codes is the finding itself. In a captured artifact the same chain isevent.ownerPath. - The
atline is the source location of the innermost located node on that chain,file:line:colrelative to the project root, as written by the compiler'ssourceNameslocationskind (on in development through@solidjs/vite-plugin). In a browser the path is printed under the dev server's origin, so the console links it to the Sources panel; on the server it is the bare path. In a captured artifact it isevent.loc. - When the finding is about a compiled JSX binding (an attribute, a class, a style, an inserted value), the entry also carries the element it writes as a second console argument: hover it to highlight the element on the page, click it to jump to the Elements panel.
- The first report of each code ends with a footer: the link to that code's page here, then the path of the repair skill installed with
solid-jsfor an agent working in the repository.
Naming your memos and effects ({ name }) turns the trailing effect in the in line into something you can search for.
Debugging reactivity walks through the common cases.
Codes
| Code | Kind | Severity | Trigger |
|---|---|---|---|
ABANDONED_FLIGHTS | responsiveness | warn | One async source abandoned 3+ flights in 1s, each superseded before landing — the request-per-keystroke signature (attribution enabled) |
ACTION_CALLED_IN_OWNED_SCOPE | write | error | action() invoked from a component body or computation |
ASYNC_OUTSIDE_LOADING_BOUNDARY | async | warn | Async computation outside Loading boundary (non-halting; root mount is deferred) |
ASYNC_STORE_SETTER | write | error | Store setter callback returned a Promise (setters are synchronous transactions) |
ASYNC_WATERFALL | perf | info, warn | 2+/3+ sequential async flights, origin-proven (attribution enabled) |
BINDING_SLOT_POSITION | ssr, render | warn, error | Binding-slot value where it cannot bind; data.reason names the position (spread is an error and throws), client reasons orphan/fill-shape |
CLEANUP_IN_FORBIDDEN_SCOPE | lifecycle | error | onCleanup inside trackedEffect/onSettled |
DYNAMIC_ASYNC_COMPONENT | ssr | error | An async dynamic() source landed on a client component function, which cannot serialize for adoption; the memo rejects (observe + dev; rejects in every tier) |
EFFECT_RELAY_TEAR | perf | info, warn | Reader ran twice for one root change because an effect relayed it; warn when derivable or repeated (attribution enabled) |
EFFECT_WRITES_OWN_SOURCE | perf | info, warn | Effect's write provably feeds back into its own inputs; info for multi-effect rings (attribution enabled) |
FALLBACK_FLASH | responsiveness | info | A Loading fallback showed for under 150ms — feedback for a wait too short to need it (attribution enabled) |
FLUSH_IN_EFFECT_CALLBACK | lifecycle | warn | flush() from an effect callback (no-op; the drain is already running) |
FRAME_MARKER_CORRUPTED | ssr | error | Frame slot range missing its end marker on the client — nesting or an HTML rewriter (observe + dev) |
GRAPH_GROWTH | perf | warn | The live graph and what outlived each navigation climbed at a route's settle on 3 visits to 1.25× — something each visit leaves behind (attribution enabled) |
HEAD_TAG_INVALID | head | warn | useHead registration the render could not honor; data.reason names the rule (dev) |
HOT_SCOPE_FANOUT | perf | warn | 5+/50+/500+ scopes hot from one root cause (attribution enabled) |
HOT_SCOPE_RERUNS | perf | warn | 120+ re-runs of one scope in 1s (attribution enabled) |
HOT_SCOPE_TIME | perf | warn | 8ms+ self-time in one scope in 1s (attribution enabled) |
HUGE_FAN_IN | graph | warn | One recompute tracked 2000 sources (always on) |
HUGE_FAN_OUT | graph | warn | One change reached 2000 live subscribers (always on), or 250 — fanOut — on a stamped root write (attribution enabled) |
IMMUTABLE_UPDATE_IN_STORE | perf | warn | Store container replaced by a mostly-identical copy (attribution enabled) |
INVALID_AFFECTS_TARGET | write | error | affects() given a key path, or a key on an accessor |
INVALID_REFRESH_TARGET | write | error | refresh() target is not a source accessor or refreshable store |
INVARIANT_VIOLATION | error | error | Internal consistency check failed (throws under __TEST__, reported in dev) |
LATE_HEADER_WRITE | head | error | Response header written after the head was sent; dropped (observe + dev; dev throws) |
LAZY_ASSET_UNMAPPED | ssr | warn | lazy() component's client assets could not be resolved for the page (dev) |
LOADING_ON_OUTSIDE_HOLD | async | warn | <Loading on> changed but its data is also read outside the boundary and holds the frame: the fallback can never be seen |
LONG_HOLD | responsiveness | info, warn | Acknowledged hold whose tail (last input → commit) ran 500ms+/1000ms+ (attribution enabled) |
LOWERCASE_EVENT_ATTRIBUTE | render | warn | A function was set on a lowercase on* (or on:) attribute, which is not an event handler in 2.0; use onXxx (dev; client) |
MISSING_EFFECT_FN | lifecycle | error | createEffect called without the effect function |
NO_OWNER_BOUNDARY | lifecycle | warn | Boundary created without owner |
NO_OWNER_CLEANUP | lifecycle | warn | onCleanup called without owner |
NO_OWNER_EFFECT | lifecycle | warn | Effect created without reactive owner |
OPTIMISTIC_REVERTED | responsiveness | info | An optimistic value the screen showed was replaced by a different one — reverted at settle, or superseded by the truth (attribution enabled) |
PENDING_ASYNC_FORBIDDEN_SCOPE | async | warn | Pending async read in trackedEffect/onSettled |
PENDING_ASYNC_UNTRACKED_READ | async | error | Reading pending async outside tracking scope |
PRELOAD_DESCRIPTOR_INVALID | head | warn | registerAsset("preload") descriptor broke a field rule; link dropped or field ignored (dev) |
PRIMITIVE_IN_EFFECT_CALLBACK | lifecycle | error | Memo, effect, or root created inside an effect callback or its cleanup, where there is no ambient owner (throws) |
PRIMITIVE_IN_FORBIDDEN_SCOPE | lifecycle | error | Reactive primitive created inside trackedEffect/onSettled |
REACTIVE_WRITE_IN_OWNED_SCOPE | write | error | Reactive write/invalidation inside component/computation |
REACTIVITY_HALTED | error | error | Uncaught error escaped every boundary; scheduling stopped (reported, cause to reportError) |
REVEAL_IN_RENDER_TO_STRING | ssr | warn | Nested <Reveal> with collapsed/together under renderToString (dev) |
RUN_WITH_DISPOSED_OWNER | owner | warn | runWithOwner with disposed owner |
SERVER_ERROR_SANITIZED | ssr | error, info | Error replaced with the generic Error on the wire — data.source "server-function" (error) or "ssr" (info); data.error is the original (observe + dev) |
SERVER_WRITE | write | warn | Signal/store/optimistic setter ran during a server render; inert, will throw (dev; once per category) |
SETTLE_WALK_UNINITIALIZED_SOURCE | lifecycle | error | Internal: settle walk reached a source that never produced a value (reported) |
SETTLED_CLEANUP_UNOWNED | lifecycle | error | onSettled returned a cleanup in an unowned (out-of-band) scope |
SILENT_HOLD | responsiveness | info, warn | Write held 100ms+/200ms+ by pending async with no on-screen acknowledgement (attribution enabled) |
SSR_BOUNDARY_WATERFALL | ssr | info, warn | A <Loading> boundary needed 3+/4+ render passes — 2+/3+ sequential async waits (dev) |
SSR_CLIENT_CONTENT_MASKED | ssr | warn | Client-only content in a <Loading> surfaced only after a server wait; the server's work was discarded (dev) |
SSR_LIVE_HOLE_LATCHED | ssr | warn | A live hole that shipped its markers was closed by the impurity gate on re-run and stopped updating (dev) |
SSR_RENDER_ERROR_CONTAINED | ssr | error | Server render error routed by a boundary: data.handling fallback / client / failed (observe + dev) |
SSR_STREAM_ABANDONED | ssr | warn | Response stream cancelled or sink failed with fragments pending (observe + dev) |
SSR_SUBTREE_ABANDONED | ssr | warn | A failed fragment's pending descendants were discarded (observe + dev) |
SSR_UNDECLARED_LIVE_SOURCE | ssr | warn | A document render is still pumping an async iterable in server-component scope 5s in — declare the function live(...) or bound the source (dev) |
STACKED_HOLDS | responsiveness | warn | 3+ interactions were waiting in one hold when it committed — repeats piled behind the same source (attribution enabled) |
STRICT_READ_UNTRACKED | strict-read | warn | Untracked reactive read in component/effect body |
SYNC_NODE_RECEIVED_ASYNC | lifecycle | error | sync: true computation returned a Promise / AsyncIterable |
UNRECOGNIZED_INSERT_VALUE | render | warn | Value at an insert position the renderer cannot render; skipped (dev; server and client) |
UNSCOPED_HOLE_ALLOCATED_IDS | render | warn | Unscoped hole was handed a function whose content took ids at a position the other side does not share; keys permute (dev) |
UNSTABLE_LIST_IDENTITY | perf | warn | mapArray/For recreated rows for equivalent items (attribution enabled) |
UNSTABLE_MEMO_OUTPUT | perf | warn | Memo returned a new-but-equivalent container 4+ runs running (attribution enabled) |
UNTRACKED_ASYNC_HANDLER | responsiveness | info, warn | Handler awaited 100ms+/200ms+ past its frame with no write before the await and no action(): a dead click no hold could judge (attribution enabled) |
UNTRACKED_READ_AFTER_AWAIT | async | warn | Async computation first read a signal/memo/store key after an await; never a dependency (dev; V8 engines only) |
WASTED_RECOMPUTE | perf | warn | 80%+ of a scope's 5+ runs in a second produced an unchanged value for 2ms+ of compute — inputs change, result does not (attribution enabled) |
WIDE_SCOPE_DEPS | perf | warn | Scope subscribed to 200+ sources (attribution cost checks on) |