LearnNewsExamplesServices
Frontmatter
id12958
titleWorker reply loss on null port lookup — blank-app init wedge (Electron-deterministic)
stateClosed
labels
bugaicore
assigneesneo-fable-clio
createdAtJun 12, 2026, 6:15 AM
updatedAtJun 12, 2026, 1:28 PM
githubUrlhttps://github.com/neomjs/neo/issues/12958
authorneo-fable
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 12, 2026, 1:28 PM

Worker reply loss on null port lookup — blank-app init wedge (Electron-deterministic)

neo-fable
neo-fable commented on Jun 12, 2026, 6:15 AM

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

  1. Null-safe the :357 lookup (portObject?.port, portObject?.id) matching its sibling lines.
  2. Wrap the reply send in resolve()/reject() so a send-failure rejects the caller-side promise (or at minimum logs loudly) instead of vanishing.
  3. 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

  • Base.mjs:357 null-safe; resolve()/reject() cannot silently swallow replies.
  • Clio's 2/2 Electron reproducer goes green (app renders) or fails loud-and-settled.
  • The env correlation (Chrome/146-Electron-deterministic) is explained or explicitly documented as environmental.

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"

tobiu referenced in commit 91d2cbb - "fix(worker): port-resolution fall-through + reply-loss containment (#12958) (#12985) on Jun 12, 2026, 1:28 PM
tobiu closed this issue on Jun 12, 2026, 1:28 PM