You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(local-mcp): make the relay always-ask gate fire for codex too
The relay always-ask permission gate keyed off Claude's `_meta.claudeCode.toolName`, which codex never writes — codex populates the neutral `_meta.posthog` channel. For codex the gate silently no-op'd, so in non-asking permission modes a relayed tool could auto-run on the user's machine without a prompt. Read the server through the adapter-neutral `readMcpToolDescriptor` (with the Claude `rawInput.toolName` fallback) so the gate fires for both adapters.
Also re-append the loopback relay entries on refresh_session (Django's refresh rebuilds the list without them, whose URL and per-run bearer live only in the agent-server), and cover both fixes plus the codex `_meta.posthog` gate shape with tests.
Generated-By: PostHog Code
Task-Id: 7c87c3a4-0be6-475e-a8ec-269140ded301
Copy file name to clipboardExpand all lines: docs/cloud-mcp-relay.md
+27-16Lines changed: 27 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -95,21 +95,30 @@ Per incoming HTTP request:
95
95
JSON-RPC *notifications* (no `id`) are relayed fire-and-forget: emit the
96
96
event, answer 202 immediately.
97
97
98
-
Liveness: relay endpoints 503 when no client can service the request,
99
-
reusing the permission relay's `hasReachableClient` (a direct SSE viewer OR
100
-
an active durable event stream). An earlier design used a stricter
101
-
`desktopSeenAt` timestamp, but in the durable-ingest topology the desktop
102
-
reads the run's stream through the agent-proxy and never connects to the
103
-
sandbox, so that stricter signal 503s every request — `hasReachableClient`
104
-
is the correct gate. Relay endpoints only exist when a desktop designated
98
+
Liveness: the relay endpoints use the permission relay's `hasReachableClient`
99
+
signal (a direct SSE viewer OR an active durable event stream) but must not
100
+
503 during the ~2 s startup window before the first client attaches — an MCP
101
+
client connects to each server once at session start, so a 503 there drops
102
+
the server for the whole run. So the endpoint tracks `everReachable` and only
103
+
503s once a client has been reachable and then went away (a genuine mid-run
104
+
desktop disconnect). Before the first client ever attaches, the request is
105
+
buffered — `broadcastEvent` already buffers-and-replays events until a
106
+
controller attaches — and resolves when the client arrives or the request
107
+
times out. (An earlier design gated purely on a `desktopSeenAt` timestamp,
108
+
which 503'd the startup handshake in the durable-ingest topology, where the
109
+
desktop reads the run's stream through the agent-proxy and never connects to
110
+
the sandbox directly; that's why the gate is `everReachable`-then-lost, not
111
+
"seen recently".) Relay endpoints only exist when a desktop designated
105
112
servers at creation, so a non-headless run is the precondition anyway. So:
106
113
107
-
- Claude gets an MCP connection error it reports cleanly, not a 60 s hang.
108
-
- Codex-style reachability probes (which treat any HTTP response as
109
-
reachable but connection failures as not — see `isMcpServerReachable` in
110
-
`packages/workspace-server/src/services/agent/agent.ts`) must be extended
111
-
in the sandbox to treat 503 from loopback relay endpoints as unreachable
112
-
when pruning for Codex sessions.
114
+
- Claude gets a clean MCP error on a genuine mid-run disconnect, not a 60 s
115
+
hang, and its session-start handshake is never dropped by a startup 503.
116
+
- Codex reachability probes treat any HTTP response (including the buffered
117
+
200 or a 503) as reachable and connection failures as not — see
118
+
`isMcpServerReachable` in
119
+
`packages/workspace-server/src/services/agent/agent.ts` — so a loopback
120
+
relay endpoint always probes as reachable and is never pruned from a Codex
121
+
session.
113
122
114
123
Headless-started runs (web/mobile/Slack) have no desktop: those creation
115
124
paths never declare relayed servers, so the endpoints simply don't exist.
@@ -232,7 +241,8 @@ persisted, with caps and the no-secrets rule above.**
232
241
233
242
| Failure | Behavior |
234
243
| --- | --- |
235
-
| Desktop offline at call time | Loopback endpoint 503s; agent sees "requires the desktop app" MCP error; Codex pruning treats the server as unreachable. |
244
+
| Desktop never attaches (offline from session start) | Request buffers until it times out (60 s) → agent gets JSON-RPC `-32001`; the endpoint does **not** 503 during startup (that would drop the server for the whole run). |
245
+
| Desktop disconnects mid-run (was reachable, now gone) | Endpoint 503s (`everReachable && !reachable`); agent sees a clean "requires the desktop app" MCP error rather than a hang. |
0 commit comments