Skip to content

fix(signals): skip the run for a re-staging effect pass - #3814

Open
brenelz wants to merge 5 commits into
solidjs:nextfrom
brenelz:fix/held-action-render-effect-3802
Open

brenelz wants to merge 5 commits into
solidjs:nextfrom
brenelz:fix/held-action-render-effect-3802

Conversation

@brenelz

@brenelz brenelz commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #3802. If a <Show> child mounts while an action holds a write, creates a memo over the held signal, and then a second, unheld signal changes before the action lands, the div's attribute render effect calls its compiled callback with undefined. The callback throws Cannot destructure property 'e' of 'undefined' and reactivity halts.

Root cause: the effect's first pass reads the held world, so it is born held (A29). Its value is staged, it has no committed value, and its first run belongs to the commit that lands the action. When title changes, recompute runs the effect again and re-stages the new value, which is correct. It also set _modified and queued runEffect, which then ran the callback with the empty committed _value.

The change adds one condition to that enqueue in recompute (core.ts): an effect that still carries a staged value (_pendingValue !== NOT_PENDING) is not queued, because commitPendingNode queues its run when the staged value commits. That run gets the latest staged value. The lane branch re-stages a born-held effect the same way, so the condition is not limited to the non-lane path. For an initialized held effect that re-stages inside its transaction's flush, the run skipped here would have re-applied the old committed value, so dropping it changes nothing visible.

How did you test this change?

  • packages/signals/tests/held-effect-restage-run-3802.test.ts covers the shape from the issue with a plain render effect. It asserts that the effect does not run while the action is pending, and runs once with [true, "Updated"] after it lands.
  • packages/web/test/held-action-memo-mount-3802.spec.tsx ports the playground through compiled JSX.
  • Both tests fail on next with the reported TypeError and pass with the change.
  • npx vitest run in packages/signals: 4942 passed, 0 failed. packages/solid: 819 passed.
  • npx vitest run in packages/web: 1107 passed, 33 failed. The same 33 fail on next without this change on my machine, which runs an older prebuilt compiler binary.
  • fix(signals): L2 lane takeover of held effects (#3766) and optimistic writes over a held row (#3796) #3812 does not cover this case: the web spec fails on its branch with the same TypeError.

Open

The change is +10 B minified on every scenario that retains signals core. Brotli moves between -36 B and +76 B depending on the scenario, and two non-floor caps are exceeded, so the size check fails until a maintainer decides on them. check-floor-caps passes. I did not raise the caps because the cost has not been accepted.

scenario next this PR cap
signals: + createStore 14,529 B 14,579 B 14,560 B
app: hydrating + every store primitive family 28,803 B 28,879 B 28,870 B

I measured four placements of the same condition in the if. Every one exceeds some cap, and the other three each exceed a frozen floor cap (core floor, hydrating, or live server components).

🤖 Generated with Claude Code

Maintainer follow-ups

Pushed by the maintainer side: merges of current next, a second test in held-effect-restage-run-3802.test.ts (the optimistic-lane variant: the one case where this PR's condition is needed over a born-held-only condition — it throws on next and with the narrower condition), and a comment wording tweak. No cap changes: under the size gate from #3821 the +10 B minified is reported as noise.

@changeset-bot

changeset-bot Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fac263d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@solidjs/signals Patch
test-integration Patch
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
solid-js Patch
@solidjs/universal Patch
todos-server-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codspeed

codspeed Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 188 untouched benchmarks


Comparing brenelz:fix/held-action-render-effect-3802 (fac263d) with next (2317623)

Open in CodSpeed

ryansolid and others added 2 commits October 5, 2026 21:53
Co-authored-by: Cursor <cursoragent@cursor.com>
The broader condition also skips the run an optimistic lane queues for a
born-held effect; that run applied an uncommitted value too. Also align
the inline comment with effect.ts ('the commit replays the effect').

Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
ryansolid added a commit that referenced this pull request Oct 6, 2026
The maintainer chose #3814 for #3802. Its tests stay as pins.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
ryansolid added a commit that referenced this pull request Oct 6, 2026
…as never committed (#3820)

A dated SPEC amendment (2026-10-06) naming the rule the create-time fixes
each implement by checking commit status: the loading-source rule, #3814
(#3802), and the pending #3800 and in-flush #3540 fixes. The same-tick
and in-flush shapes are recorded as not yet one-way and pinned it.fails
in tests/direction-rule-probe.test.ts; the boundary shape passes today.
Cross-references in INTERNALS-ASYNC-STATE.md §0 and the carve plan.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants