Repository navigation
[2.0 / next] probing leaks into a memo's pass pulled inside isPending — a nested latest() joins the pass to the hold #4429
Description
Summary
isPending(fn) sets the module flag probing for the duration of fn. When a read inside fn pulls a dirty memo, verdictValue closes the window for the memo's pass (setVerdict(null) — A31: "the pulled memo's pass is its own, not the probe's") but leaves probing set. If that pass calls latest() itself, latest re-opens verdictValue with probing still true, and the pulled memo's read is treated as the outer probe's:
- a held node born into the future (
CONFIG_HELD, uninitialized, staged) read throughlatest()takes theprobing && ownedarm andjoinPasses the pulled memo to the hold (REACTIVE_JOINED) — the memo's pass joins a world its own read (latest, a verdict read) does not derive from; probeFoundis set from inside the pulled pass (heldNotFinal,flying, a liveaffects()mark), so the outerisPendinganswers for reads the pulled memo made, not for the memo it read.
The same memo pulled by a plain read (or already clean) does neither. The pass's world depends on who woke it.
Reproduction
async-gen seed 7 case #1691 (packages/web/test/async-gen):
const sc = fc.sample(scenarioArb(), { seed: 7, numRuns: 1692 })[1691];
await runScenario(sc);[s0=0 m0=memo(s0) m1=memo(latest(m0)+m0 !%4=1) m2=memo(s0 !%4=2)]
<Show when=latest(m2) fb>
<span split[isPending(m0),isPending(s0)]>m0()</span>
<span split[latest(s0)]>isPending(m1)</span>
</Show>
:: t A(s0=1) s0=2 s0=3 f s0=0 t s0=1 s0=3 m t m
scenario JSON
{"signals":[0],"memos":[{"async":false,"deps":[{"src":{"k":"s","i":0},"how":"plain"}],"poison":null},{"async":false,"deps":[{"src":{"k":"m","i":0},"how":"latest"},{"src":{"k":"m","i":0},"how":"plain"}],"poison":1},{"async":false,"deps":[{"src":{"k":"s","i":0},"how":"plain"}],"poison":2}],"tree":{"t":"show","when":{"how":"latest","src":{"k":"m","i":2}},"child":{"t":"group","kids":[{"t":"leaf","text":[{"how":"plain","src":{"k":"m","i":0}}],"attrs":[{"how":"pending","src":{"k":"m","i":0}},{"how":"pending","src":{"k":"s","i":0}}],"grouped":false},{"t":"leaf","text":[{"how":"pending","src":{"k":"m","i":1}}],"attrs":[{"how":"latest","src":{"k":"s","i":0}}],"grouped":false}]},"fallback":true},"steps":[{"t":"task"},{"t":"action","s":0,"v":1},{"t":"write","s":0,"v":2},{"t":"write","s":0,"v":3},{"t":"flush"},{"t":"write","s":0,"v":0},{"t":"task"},{"t":"write","s":0,"v":1},{"t":"write","s":0,"v":3},{"t":"micro"},{"t":"task"},{"t":"micro"}]}isPending(m1) in a render effect pulls m1; m1's pass calls latest(m0) with m0 held and uninitialized in the flush's own transaction (born into it). Stack: joinPass < verdictValue (the uninitialized-held arm, probing) < read < latest < m1's fn < recompute < pullComputed < verdictValue < read < isPending.
Found by the S1 reader-seat differential (SEAT_SHADOW_REPORT): class Vl4 P/P — world after the read (old 2, new 0; Vl posture; s h u · vseat ahead oldVlane flushTx flushIsElTx), 1 instance at 2k cases × seed 7; none in the signals, solid or web suites. The slot agrees (the staging, P); the pass's world does not. No end-screen difference in this case.
Why it is a bug
- A31: a memo's pass pulled inside a window is its own; the window answers for what the pass produced.
verdictValueapplies this to the dispatch (setVerdict(null)) and not toprobing/probeFound, so a nestedlatest()inside the pulled pass re-enters the window with the outer probe's state. - F-vworld (2026-10-29): a verdict read leaves marks and does not raise the pass's world;
latest()of a born-into-the-future node is a verdict read whichever pass makes it.
Intended fix
The reader-seat rebuild S1 (#4395 → S1 PR) decides the pass's world from its seat and the read's posture (row Vl4), not from a flag the puller left set. Until the swap lands, the shadow carries this as a known class (shadowKnown, this issue). (probeFound's leak is the same bug's other half; the differential compares worlds and slots, not probe answers — the swap should save/clear probing and probeFound around the pulled pass as it does verdict.)
Activity
[-]signals: `probing` leaks into a memo's pass pulled inside `isPending` — a nested `latest()` joins the pass to the hold[/-][+][2.0 / next] `probing` leaks into a memo's pass pulled inside `isPending` — a nested `latest()` joins the pass to the hold[/+]seat(S1 M2a): exact marks in the slot table, compared against today's…
test(signals): reader seat — the slot table and shadow differential a…
fix(signals): close only the probe's state around a pulled memo (#4429)
chore(size): raise the compiled live page's cap for #4429 (+13 B mini…
chore(size): raise the live server components floor for #4429 (+13 B …
test(signals): drop the seat shadow's known entry for #4429
fix(signals): close the probe's state around a memo isPending pulls (#…
ryansolid commentedon Oct 30, 2026
Fixed on
nextby #4465 (ab3d2ad); it ships in the next RC.Generated by Claude Code
Merge remote-tracking branch 'origin/next' into claude/project-thread…