LearnNewsExamplesServices
Frontmatter
id15684
titleOperator-launched OpenCode desktop: wake envelope has no boot/session boundary (#15677 successor)
stateClosed
labels
bugai
assigneesneo-kimi-phoebe
createdAtJul 22, 2026, 3:56 AM
updatedAtJul 26, 2026, 3:27 AM
githubUrlhttps://github.com/neomjs/neo/issues/15684
authorneo-kimi-phoebe
commentsCount6
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 26, 2026, 3:26 AM

Operator-launched OpenCode desktop: wake envelope has no boot/session boundary (#15677 successor)

neo-kimi-phoebe
neo-kimi-phoebe commented on Jul 22, 2026, 3:56 AM

Context

Successor to #15677 (which takes the Fleet-managed headless topology + the two topology-independent fences). This ticket owns the topology the 2026-07-22 outage actually happened in: the operator-launched OpenCode desktop (GUI app, NodeService-spawned server on a random port). The operator's follow-up question is this ticket's premise: "you solved the missing wake delivery for the current session — is this also resolved for future sessions?" Answer at filing: no.

Discovery complete (2026-07-22, this seat's live host). The boundary question now has a verified answer with two repair paths and one rejected one; the original Out-of-Scope exclusion of the plugin path is reversed (see below).

The Failure Modes (this topology, today)

  1. Desktop restart (the 01:11Z outage class): new random port + rotated password + new session id → the envelope is fully stale → daemon retries ×5 against the dead port → wake dropped, log-only. Evidence chain in #15677 (Jul-18 envelope vs 12:39AM reboot; the dropped CHANGES_REQUESTED wake).
  2. New session within the same boot: the healed envelope binds sessionId to the session that was live at heal time. A new operator session keeps port + credentials but the envelope still targets the OLD session id — wakes land in a dead/idle transcript, or fail against a closed session. This is the desktop-specific retarget mode, adjacent to the split-brain fence (#15677 / #15665 invariant): the binding is per-session, and nothing owner-native re-binds it.
  3. Silent until noticed: both modes fail invisibly until a human wonders why no wakes land (Fence 2 from #15677 covers the drop case; mode 2's land-in-zombie-session case may not even produce a drop signal).

The Discovery (verified 2026-07-22, live host)

Session identity is NOT discoverable daemon-side. GET /session?directory=<seat-checkout> returns 11 sessions in this seat's checkout — every session the seat has ever run accumulates there. "Latest updated" selects the current one today, but it is a heuristic that provably fails the twin class (Iris's serverside sibling was exactly a second live writer in one directory). The #15677 fence holds: no listing-based session inference, ever.

The two owner-native boundaries that CAN exist:

  1. Agent-side boot self-write (immediate, zero-dependency). The running session refreshes the envelope at session start: at boot time the writing session IS the latest-updated one by construction (its own boot turn just updated its timestamp), so a self-write is owner-native for that moment. Verification: self-inject a probe after writing (204 + in-session landing — the exact heal verification used 2026-07-22). Caveat: two live sessions both running this ritual race last-writer-wins (the twin case, itself tracked in #15665); bounded because a self-write happens only at boot, never periodically.
  2. Plugin self-announce (upstream-correct). The desktop's plugin runtime fires session.created with the session id — the session announcing itself, no listing, no timestamp guessing. The existing writer (~/.config/opencode/plugins/neo-wake-envelope.mjs) is in place and import-free; it never loads because the desktop's background dependency install fails resolving @opencode-ai/plugin@local (NpmInstallFailedError: No matching version found for @opencode-ai/plugin@local, opencode.log WARN). The local tag is unresolvable; the published line exists (@opencode-ai/plugin@1.18.4 on npm).

The belt (daemon-side, coordinate-only): on connection-refused, the daemon may refresh port (lsof) + credentials (seat env) — never the session id. Coordinates fail soft; identity fails closed.

The Reversal

The original Out-of-Scope said "this ticket does not resurrect the plugin path; the boundary must live outside the plugin's reliability." Discovery reverses that: the plugin path is the ONLY fully owner-native boundary, and its blocker is a bounded, validated resolution failure (a version pin, not a redesign). The ticket now owns the plugin repair as the primary path, with the agent-side boot self-write as the immediate path that needs no desktop fix at all.

The Fix

  1. Immediate — agent-side boot self-write: add envelope refresh to the seat's boot protocol (context-recovery/boot sequence): query the server for the seat-checkout session list, bind the latest-updated (which at boot time is the writing session by construction), write the envelope atomically (0600), self-inject a probe, and report the outcome to the mailbox/boot log. Generalized to future seats via #15697's seat generator (Iris's lane — this discovery is her input).
  2. Primary — plugin-runtime resolution repair: pin @opencode-ai/plugin to the published line (^1.18.4) via ~/.config/opencode/package.json so the background installer resolves (validated: npm install in the config dir resolves 1.18.4 cleanly; bun install equivalent per OpenCode's plugin docs). Verify on the next desktop restart (operator-owned) that the background install succeeds, the plugin loads, and session.created writes the envelope.
  3. Belt — daemon coordinate refresh: on connection-refused delivery, refresh port + credentials from seat env before the retry loop; never touch session id.
  4. Fence (unchanged): anything that is not a session self-announce or an agent-side boot self-write fails closed with the #15677 Fence-2 degraded-health signal — never a listing-based guess.

Acceptance Criteria

  • Boot self-write: a desktop restart + new session lands a correct envelope with zero human intervention (receipt: envelope sessionId == the live session; probe prompt lands in-session)
  • Boot self-write verification: the probe result is logged/visible (204 + in-session landing or a loud degraded note)
  • Plugin path: on a desktop restart after the version pin, the background dependency install succeeds (no @local WARN), the plugin loads, and session.created writes the envelope (operator-owned receipt)
  • [L3-deferred — operator handoff needed] (added 2026-07-26, PR #15944): desktop-restart verification of the shipped dependency-free plugin — the neo-wake-envelope plugin loaded … restore coverage armed line appears in opencode.log after the next restart (settles loaded-vs-silent), and a restored session's first session.updated produces written-probed for restored session … (the restore-gap close). This line carries the only operator-owned validation pointer; it survives any Resolves close.
  • Daemon belt: a stale-envelope connection-refused triggers at most one coordinate refresh (port+creds) before drop; session id is never modified by the belt
  • Twin-race caveat recorded: two live sessions both self-writing race last-writer-wins (accepted, tracked by #15665)
  • Falsifiers A/B/C from #15677 hold (no sibling/child/guess retargeting)

Out of Scope

  • The Fleet-managed headless topology (#15677 primary)
  • The kimi-server harness (#15665)
  • Desktop-upstream fixes beyond the version pin (if the desktop's background installer still tries @local despite the pin, the residual is an upstream OpenCode issue, noted for the operator)
  • The twin-race arbitration itself (#15665)

Ownership map (added 2026-07-22 after PR #15729)

  • Boot self-write (this ticket's immediate path): delivered in PR #15729 (the refresh script + the context-recovery step 0 + witnesses).
  • Plugin self-announce path: the @opencode-ai/plugin@^1.18.4 pin is validated locally; the generator-side emission lives in #15697's seat-config generator (Iris's lane — the discovery was handed to her).
  • Daemon coordinate belt: owned by #15677's Fence 1 implementation (adapter stale-recovery).

Related

  • #15677 (primary topology + both fences; this is its named desktop successor)
  • #15481 (the hook writer — the Fleet-side artifact)
  • #15665 (shared no-heuristic-retarget invariant + the twin case)
  • #15697 (seat generator — the boot self-write generalization target; Iris's lane)
  • #15392-arc (OpenCode seat productization context)

Live latest-open sweep: checked latest 20 open issues at 2026-07-22T01:56Z (creation) and re-verified at amendment; #15677/#15665 adjacent, neither covers this topology. A2A in-flight sweep: no competing claim.

Origin Session ID: 72c8c42d-f18a-408c-97c8-aeb1f82dd276

Retrieval Hint: "opencode desktop wake envelope boot boundary session retarget future sessions restart successor plugin local resolution agent self-write"