Skip to content

App-host (AppView) RPC export table goes stale during long sessions; page can no longer call backend APIs #53

Description

@yorkane

Environment

  • OpenCodex gateway: 2.1.0 (f6fd79c) + PR fix(compatibility): support delegated worktree shell environment pattern #52 (b5c61e2), Linux, headless Electron runner
    (.data/runtime/official-electron-runner/linux-x64/opencodex-gateway, flags: --no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage --headless --disable-gpu)
  • Official runtime: ChatGPT Desktop for Linux 26.908.40834 (build 318576267-1789148373000); also reproduced on 26.903.61454, 26.908.31748, 26.820.71523
  • Access: browser (web-shell) over LAN, single user, gateway bound to 127.0.0.1

Symptom

After a session has been running for a while (idle periods produce nothing), the gateway log starts
spewing and never recovers until the gateway is restarted:

Failed to register AppView RPC services errorMessage="no such export ID: 622"
    at oee.ensureResolvingExport (.../.data/cache/codex-official-bundle/.vite/build/src-DJnwJvdz.js:709:36337)
Failed to register AppView RPC services errorMessage="no such entry on exports table: -1"
    at e.evaluateImpl (...)
Failed to register AppView RPC services errorMessage="MessagePort message error."
[electron-message-handler] sa_server_request_failed attachAuth=true attachIntegrityState=true
    error={"name":"Error","message":"no such export ID: 1", ...}
[electron-message-handler] Failed to broadcast thread state patches conversationId=01a04794-... error=no such export ID: 1

User-visible effect: the page can no longer perform backend API requests (usage / account / remote
control etc.), and thread state / activity stops syncing for the affected conversations.
Reloading the page does NOT recover it (measured: +4 sa_server_request_failed after Page.reload);
only systemctl restart ryensx-opencodex.service clears it.

Counts (single box, ~6 days, one user)

signal total
Failed to register AppView RPC services 5359
sa_server_request_failed (no such export ID: 1) 37924
Failed to broadcast thread state patches 91480 (concentrated on 9 conversations, max 532 in one)

Idle 3-minute window: 2 new log lines, 0 failures → this is not a retry loop, it is accumulated state.

Direct evidence of the trigger

Every burst begins immediately after the gateway drops an app-host response to a dead client:

[2026-09-14T04:14:52.396Z] [ws-hub] send_to_missing_client {"channel":"codex_desktop:message-for-view","type":"mcp-response","requestId":"08fa6265-..."}
[2026-09-14T04:14:52.396Z] [ws-hub] send_to_missing_client {"channel":"codex_desktop:message-for-view","type":"mcp-response","requestId":"84bef983-..."}
[2026-09-14T04:14:52.399Z] [ws-hub] send_to_missing_client {"channel":"codex_desktop:message-for-view","type":"mcp-response","requestId":"bef99bc5-..."}
Failed to register AppView RPC services errorMessage="no such entry on exports table: -1"
Failed to register AppView RPC services errorMessage="no such export ID: 622"
...
(node:3185805) MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
                 65 destroyed listeners added to [WebContents]. MaxListeners is 64.

Note the MaxListenersExceededWarning on [WebContents] (65 destroyed listeners): one AppView appears
to be registered per browser connection on the shared hidden webContents and they are never released
(the hidden window is never destroyed), while no such export ID: 1 shows the page-side RPC peer is
calling into an export table that no longer exists.

What we tried to reproduce (all clean, so it is accumulation, not a single code path)

probe (Chrome for Testing via CDP) result
fresh page load, 25 s idle 0
load → cut network 20 s (WS drops) → restore (page auto-reconnects, no reload) 0
page A open, then page B opens (second tab), then B closes, then reload A 0 AppView, small sa_server_request_failed drift
normal human usage over hours/days reproduces, then permanent until gateway restart

Suspects

  1. rememberRequestRoute(channel, invokeArgs, clientId) keeps routing to a clientId that no longer has a
    live socket → send_to_missing_client drops mcp-response frames → the page-side app-host RPC never
    completes its export registration. Related history: issue Chat history disappears after browser WebSocket reconnect #27 / PR fix(gateway): restore app-host relay after websocket reconnect #28 (restore app-host relay after websocket reconnect).
  2. Official main registers an AppView per codex_desktop:connect-app-host on the shared hidden webContents
    and only cleans up on webContents destroyed, so registrations accumulate (65 > 64 listeners).
    Would the gateway need to dispose the previous AppView explicitly when a page reconnects / a relay is replaced?

Current mitigation

A 3-minute watchdog restarts the gateway when the per-interval delta of these log lines exceeds a
threshold (AppView ≥ 30 or sa_server_request_failed ≥ 90). It works, but it is a sledgehammer:
every recovery costs a ~60-90 s gateway restart and a page refresh.

Questions

  • Is there an intended way for the gateway to tear down / rebind an AppView for a still-alive hidden
    webContents (e.g. when closeAppHostRelays(ws, "replaced") runs)?
  • Should mcp-response frames be dropped (and the route purged) once a clientId has no live socket,
    or should the request be failed back to the caller instead?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions