Skip to content
This repository was archived by the owner on Aug 17, 2026. It is now read-only.

fix(tui): attach explicitly subscribes to /global/event SSE - #2

Merged
terrxo merged 1 commit into
devfrom
fix/attach-subscribe-all-session-messages
May 26, 2026
Merged

terrxo merged 1 commit into
devfrom
fix/attach-subscribe-all-session-messages

Conversation

@terrxo

@terrxo terrxo commented May 26, 2026

Copy link
Copy Markdown

Problem

When opencode runs in attach mode (opencode attach <url>), externally-triggered session activity never renders in the TUI. Specifically: if peer A POSTs to /session/<id>/prompt_async while peer B is attached to that same session, the daemon processes the wake (the LLM runs, the assistant message is written to opencode.db) but peer B's screen shows nothing.

Verified in production by grunt-it (hivemind anomalyco#221):

  • daemon log shows service=bus type=message.updated publishing after the POST
  • curl <daemon>/session/<id>/message returns the new messages
  • attach client TUI displays nothing
  • only ONE TCP connection per attach client to the daemon (the RPC channel) — no SSE connection observed

Root cause

attach.ts calls tui({...}) without passing events. The SDKProvider's onMount has a fallback path:

if (props.events) {
  const unsub = await props.events.subscribe(handleEvent)
  ...
} else {
  startSSE()
}

startSSE() is an async IIFE that calls sdk.global.event({ sseMaxRetryAttempts: 0 }) and forwards events to the local emitter. In practice this fallback is silent on failure — the outer .catch(() => {}) (line 108 of context/sdk.tsx) swallows every error including the SSE-connect failure I observed in production.

Fix

attach.ts now explicitly creates an EventSource that subscribes to /global/event via the SDK, mirroring how the local-worker case in thread.ts wires events: createEventSource(client). Caller (SDKProvider) sees props.events defined and uses the explicit subscription instead of the silent fallback.

The new createAttachEventSource is intentionally one-shot (no reconnect loop). Reconnect-on-disconnect can be a follow-up; the silent fallback is the immediate bug.

Verification

  • bun typecheck from packages/opencode passes.
  • End-to-end manual test requires a rebuilt binary; tracked in hivemind #222 Phase 2 patch 2c verification.

Notes for upstream (anomalyco/opencode)

Will open an equivalent PR against anomalyco/opencode:dev once we've verified the fix end-to-end on a built binary. The bug exists in upstream too.

Without this, externally-triggered session activity (a wake fired via
POST /session/<id>/prompt_async from another peer) never renders in
attach client TUIs. The wake processes server-side (LLM runs, message
written to db), but the attach client has no way to learn about it.

## Root cause

attach.ts called tui() without passing `events`. The SDKProvider had a
fallback path (startSSE() in context/sdk.tsx onMount) that should have
subscribed to /global/event when props.events was undefined. In practice
that fallback is silent on failure — its async IIFE catches every error.

This patch explicitly creates an EventSource in attach.ts that subscribes
to /global/event via the SDK and forwards events to the SDKProvider,
mirroring how the local-worker case in thread.ts wires `events: createEventSource(client)`.

## Verification

Local typecheck passes (`bun typecheck` from packages/opencode).

Manual end-to-end test plan (requires rebuilt binary):
1. Start `opencode serve --port 4096` in one terminal
2. `opencode attach http://127.0.0.1:4096 --dir <project>` in another
3. From a third terminal: `curl -X POST 'http://127.0.0.1:4096/session/<id>/prompt_async' -H 'Content-Type: application/json' -d '{"parts":[{"type":"text","text":"wake test"}]}'`
4. The attach client TUI should render the new user message + assistant response

Refs hivemind anomalyco#221 (wake-up display bug verified in production) and anomalyco#222
Phase 2 patch 2c.
@terrxo

terrxo commented May 26, 2026

Copy link
Copy Markdown
Author

✅ Verified end-to-end 2026-05-26T16:14Z

Built attach client from this branch via bun run --cwd packages/opencode --conditions=browser src/index.ts attach http://127.0.0.1:4096 --dir /tmp in a tmux session, against a running opencode serve daemon on :4096.

Setup confirmed

  • daemon log shows: INFO 16:14:12 service=server global event connected ← the patched attach client's SSE subscription firing on startup
  • patched client (bun.exe PID 70358) opens 11 ESTABLISHED connections to :4096, vs the un-patched system clients which open only 1

External wake test

  1. Started a fresh session in the patched TUI by typing 'hi' → got assistant reply 'Hi! What can I help you with?' ✓
  2. New session created: ses_19aeef126ffeS8iwZzVjOm5e7Q
  3. From OUTSIDE the TUI process, fired: curl -X POST http://127.0.0.1:4096/session/ses_19aeef126ffeS8iwZzVjOm5e7Q/prompt_async -H 'Content-Type: application/json' -d '{"parts":[{"type":"text","text":"EXTERNAL WAKE TEST: ..."}]}' → HTTP 204
  4. In the TUI tmux pane, the external prompt appeared as a user message AND the LLM responded with WAKE-RENDERED-OK — exactly the response the wake-prompt requested.
┃  EXTERNAL WAKE TEST: this prompt was POSTed via curl from outside the TUI.
┃   If you see this text appear in the TUI and a response starts streaming,
...

   WAKE-RENDERED-OK

Comparison with un-patched system opencode

The same external wake fired against an un-patched attach client (verified earlier in hivemind #221) — daemon processed the wake, LLM ran, assistant message written to opencode.db, but nothing rendered in the TUI. Patched build: renders correctly.

This unblocks the hivemind autonomous-coordinator vision: peers can now wake each other and Nik sees the activity in real time without manually polling the daemon or opening hivemind-ui.

@terrxo
terrxo merged commit f6ca7a7 into dev May 26, 2026
@terrxo
terrxo deleted the fix/attach-subscribe-all-session-messages branch May 26, 2026 16:15
terrxo added a commit that referenced this pull request May 28, 2026
Without this, externally-triggered session activity (a wake fired via
POST /session/<id>/prompt_async from another peer) never renders in
attach client TUIs. The wake processes server-side (LLM runs, message
written to db), but the attach client has no way to learn about it.

## Root cause

attach.ts called tui() without passing `events`. The SDKProvider had a
fallback path (startSSE() in context/sdk.tsx onMount) that should have
subscribed to /global/event when props.events was undefined. In practice
that fallback is silent on failure — its async IIFE catches every error.

This patch explicitly creates an EventSource in attach.ts that subscribes
to /global/event via the SDK and forwards events to the SDKProvider,
mirroring how the local-worker case in thread.ts wires `events: createEventSource(client)`.

## Verification

Local typecheck passes (`bun typecheck` from packages/opencode).

Manual end-to-end test plan (requires rebuilt binary):
1. Start `opencode serve --port 4096` in one terminal
2. `opencode attach http://127.0.0.1:4096 --dir <project>` in another
3. From a third terminal: `curl -X POST 'http://127.0.0.1:4096/session/<id>/prompt_async' -H 'Content-Type: application/json' -d '{"parts":[{"type":"text","text":"wake test"}]}'`
4. The attach client TUI should render the new user message + assistant response

Refs hivemind anomalyco#221 (wake-up display bug verified in production) and anomalyco#222
Phase 2 patch 2c.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant