Context
Follow-up leaf from PR #12956 (the #12946 wedge fix). The multi-agent hunt's reply-plumbing sweep CONFIRMED a fourth hole that #12956 deliberately did not fix (scoped as SharedWorker-only at the time) — and @neo-fable-clio then hit it live with a deterministic reproducer while standing up the #12947 recount: fresh session, root viewport isVdomUpdating: true / vnode: null forever, app never renders, ZERO errors anywhere — 2/2 reproducible in the Claude preview browser (Chrome/146 Electron) while Chrome/149 sessions render fine. Full signature + recipe: https://github.com/neomjs/neo/issues/12946#issuecomment-IC_kwDODSospM8AAAABF19mfw (her evidence comment on the closed root ticket). This is very likely the long-observed "preview pane loads a blank app / worker alive, DOM dead" friction class agents have been working around with context restarts.
Release classification: post-release (Approve+Follow-Up follow-up from PR #12956 — non-blocking for v13: the #12956 watchdog now NAMES the wedged component within 5s, converting silent-blank into a diagnosable error; the affected surface is the agent preview env, not the product runtime).
The Problem
src/worker/Base.mjs:357: the reply path's port lookup lacks null-safety — let portObject = me.getPort({id: opts.port}); port = portObject.port throws TypeError when getPort returns null (disconnected/never-registered port). The exception escapes RemoteMethodAccess#resolve() (src/worker/mixin/RemoteMethodAccess.mjs:299 — no try/catch), the reply never posts, and the requesting worker's promise never settles. Compare the adjacent lines :360/:365 which already use portObject?.port safely. A lost Helper.create reply at INIT = the blank-app flavor: flag stuck true, vnode null, nothing ever renders.
The Architectural Reality
src/worker/Base.mjs:356-357 (the unsafe lookup; the incoming msg.port is copied to opts via assignPort, RemoteMethodAccess.mjs:316, and can reference a port that disconnected between receipt and reply).
RemoteMethodAccess#resolve()/reject() (:270/:299): no exception handling around sendMessage — any throw silently swallows the reply.
- Environment correlation (Chrome/146 Electron deterministic vs Chrome/149 fine) is unexplained — the falsifier for "why does THIS env always produce the null port at init" is part of the work.
The Fix
- Null-safe the
:357 lookup (portObject?.port, portObject?.id) matching its sibling lines.
- Wrap the reply send in
resolve()/reject() so a send-failure rejects the caller-side promise (or at minimum logs loudly) instead of vanishing.
- Reproduce via Clio's recipe in the Electron preview env; confirm the blank-app case either renders or fails LOUDLY with a settled promise.
Acceptance Criteria
Out of Scope
- The apply-boundary containment, catch hardening, and watchdog (shipped in PR #12956).
- The queued-promise reject gap (#12957, sibling leaf).
Related
PR #12956 (parent fix), #12946 (closed root; carries the reproducer evidence), #12957 (sibling leaf), #12947 (where the reproducer was found).
Live latest-open sweep: checked latest 20 open issues at 2026-06-12T04:14Z; no equivalent found. A2A in-flight sweep: clean (the reproducer's author explicitly deferred the lane: "evidence, not prescription — your lane").
Origin Session ID: e605ce21-3668-445c-bc00-45896aa9a092
Retrieval Hint: "port null reply loss blank app init wedge Electron preview Base.mjs 357"
Context
Follow-up leaf from PR #12956 (the #12946 wedge fix). The multi-agent hunt's reply-plumbing sweep CONFIRMED a fourth hole that #12956 deliberately did not fix (scoped as SharedWorker-only at the time) — and @neo-fable-clio then hit it live with a deterministic reproducer while standing up the #12947 recount: fresh session, root viewport
isVdomUpdating: true/vnode: nullforever, app never renders, ZERO errors anywhere — 2/2 reproducible in the Claude preview browser (Chrome/146 Electron) while Chrome/149 sessions render fine. Full signature + recipe: https://github.com/neomjs/neo/issues/12946#issuecomment-IC_kwDODSospM8AAAABF19mfw (her evidence comment on the closed root ticket). This is very likely the long-observed "preview pane loads a blank app / worker alive, DOM dead" friction class agents have been working around with context restarts.Release classification:post-release (Approve+Follow-Up follow-up from PR #12956 — non-blocking for v13: the #12956 watchdog now NAMES the wedged component within 5s, converting silent-blank into a diagnosable error; the affected surface is the agent preview env, not the product runtime).The Problem
src/worker/Base.mjs:357: the reply path's port lookup lacks null-safety —let portObject = me.getPort({id: opts.port}); port = portObject.portthrowsTypeErrorwhengetPortreturns null (disconnected/never-registered port). The exception escapesRemoteMethodAccess#resolve()(src/worker/mixin/RemoteMethodAccess.mjs:299— no try/catch), the reply never posts, and the requesting worker's promise never settles. Compare the adjacent lines:360/:365which already useportObject?.portsafely. A lostHelper.createreply at INIT = the blank-app flavor: flag stuck true, vnode null, nothing ever renders.The Architectural Reality
src/worker/Base.mjs:356-357(the unsafe lookup; the incomingmsg.portis copied to opts viaassignPort,RemoteMethodAccess.mjs:316, and can reference a port that disconnected between receipt and reply).RemoteMethodAccess#resolve()/reject()(:270/:299): no exception handling aroundsendMessage— any throw silently swallows the reply.The Fix
:357lookup (portObject?.port,portObject?.id) matching its sibling lines.resolve()/reject()so a send-failure rejects the caller-side promise (or at minimum logs loudly) instead of vanishing.Acceptance Criteria
Base.mjs:357null-safe;resolve()/reject()cannot silently swallow replies.Out of Scope
Related
PR #12956 (parent fix), #12946 (closed root; carries the reproducer evidence), #12957 (sibling leaf), #12947 (where the reproducer was found).
Live latest-open sweep: checked latest 20 open issues at 2026-06-12T04:14Z; no equivalent found. A2A in-flight sweep: clean (the reproducer's author explicitly deferred the lane: "evidence, not prescription — your lane").
Origin Session ID: e605ce21-3668-445c-bc00-45896aa9a092
Retrieval Hint: "port null reply loss blank app init wedge Electron preview Base.mjs 357"