Repository navigation
Description
Reproduced on next at 2a0da2f (2026-10-24), with solid-js, @solidjs/web and @solidjs/compiler built from source. Run: node dv12-hydrate/run.mjs (needs playwright and esbuild). The harness _hydrate.mjs is the same one as in #4272.
Expected (D-39). A boundary recovers when the failed content's
inputs change and a good answer lands.
Shape. <Errored><p>{shown()}</p></Errored>; shown throws when
item is null; the server's answer is null, so the server draws the
fallback. The client hydrates; setSel(3); i:3 is asked and lands
"pear".
Observed. Hydrated: "item 4 not found" stays. Rendered on an empty
page: "pear". hydratedCreateErrorBoundary throws the serialized error
on its first run instead of running the content, so the boundary tracks
nothing.
Repro
dv12-hydrate/app.jsx
import { createSignal, createMemo } from "solid-js";
import { markSafeError } from "@solidjs/web";
export function App(props) {
const [sel, setSel] = createSignal(4); props.expose({ setSel });
const item = createMemo(() => props.ask("i:" + sel()));
const shown = createMemo(() => { const v = item(); if (v == null) throw markSafeError(new Error(`item ${sel()} not found`)); return v; });
return <Errored fallback={e => <p>{e().message}</p>}><p>{shown()}</p></Errored>;
}dv12-hydrate/run.mjs
// A boundary the server rendered in its error state; the client hydrates it, then the input changes and the new answer
// lands. Expected (as when the client renders the same page itself): "pear". Observed after hydrating: the failure stays —
// the boundary was hydrated by throwing the server's error, so it tracks nothing and never runs its content again.
import { harness, wait } from "../_hydrate.mjs";
const h = await harness(new URL(".", import.meta.url).pathname, () => Promise.resolve(null));
for (const mode of ["hydrate", "render"]) {
const page = await h.open(mode);
await page.evaluate(m => window.repro.start(m), mode); await wait(30);
if (mode === "render") { await page.evaluate(() => window.repro.land("i:4", null)); await wait(30); }
const before = await page.evaluate(() => window.repro.text());
await page.evaluate(() => window.repro.call("setSel", 3)); await wait(30);
const asked = await page.evaluate(() => window.repro.land("i:3", "pear")); await wait(60);
console.log(mode.padEnd(8), `shows "${before}"`, `· i:3 asked: ${asked}`, `· after it lands: "${await page.evaluate(() => window.repro.text())}"`, `(expected "pear")`);
}
await h.close();Found by ReactiveScript's conformance suite, which diffs its spec against Solid's SCENARIOS.md.
Activity
test(solid): hydrated error boundary holds the client's own error (#4273
wip(signals,solid): hold the server's fallback over clean hydrated co…
wip(solid): move the hydrated Errored hold into the hydration module (#…
wip(solid): V1 — hold ends only on reset() or a content re-run (#4273)
wip(solid): V2 — clean content swaps in at hydration end (#4273)
wip(solid): V3 — V1 plus a minimal effect re-run hook ending the hold (…
fix(solid): hydrate an errored boundary's content in the pass (#4273)
perf(solid): golf the hydrated Errored hold under the size caps (#4273)
fix(solid): hydrated Errored boundary runs its content in the hydrati…
ryansolid commentedon Oct 26, 2026
Fixed on
nextby #4279 (0856fee); it ships in the next RC.Generated by Claude Code
fix(solid): an Errored around a failing streamed Loading draws its fa…
fix(solid,web): a failed Errored waits for its fallback's async holes…
fix(solid): hydration never asks again for an answer the server alrea…
fix(solid): a rejected fragment's fresh render claims what the server…
fix(hydration): a write before a rejected fragment's chunks recovers …