From ee2141153ff60359f86324e8c6022ca53725b494 Mon Sep 17 00:00:00 2001 From: Will Wiriawan Date: Tue, 1 Sep 2026 13:13:42 +0700 Subject: [PATCH] fix(cli): desktop-sandbox diagnostics for broker commands + doctor --fix Running broker lifecycle commands from sandboxed agents (Codex desktop) produced three confusing failures: status probed loopback and reported the broker down on `fetch failed`, broker start died with a bare EPERM writing its log, and `c2c doctor --fix` was rejected because repair is the implicit default. - doctor accepts an explicit --fix alongside --no-fix. - broker status distinguishes "runtime file exists but probe failed" (sandbox suspicion, exit 1, connector URL from runtime) from actually not running. - ensureBroker wraps log-open failures with actionable escalation guidance instead of a bare EPERM. - SKILL.md documents the sandbox behavior for desktop agents. --- skill/SKILL.md | 1 + src/broker/daemon.ts | 13 ++++++++++++- src/cli/index.ts | 45 ++++++++++++++++++++++++++++++-------------- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/skill/SKILL.md b/skill/SKILL.md index 6906622..2e86f34 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -38,6 +38,7 @@ When the user starts a session in a project, register it and learn its identity: 3. Remember this workspace `workspaceId` — include it whenever you tell the user what to ask Claude, and use it in INIT instructions (Claude scopes every tool call with it). 4. When a Codex session ends, run `c2c use --end --json` to clear the local session binding. 5. Route repairs through `c2c doctor --json` (installation-aware) or `c2c broker status`; per-project bridges (`c2c start`) are legacy compatibility only. +6. In the Codex desktop app, sandboxed commands may fail with EPERM or `fetch failed` when they touch the broker (loopback requests, daemon spawn, state writes). The broker is a system service that is usually already running — first try `c2c broker status`; if it reports the state as unclear, rerun the command with sandbox escalation approved, or in a regular terminal. Do not conclude the broker is down from a sandboxed failure alone. ## Planning loop diff --git a/src/broker/daemon.ts b/src/broker/daemon.ts index 5e92101..8d0c53b 100644 --- a/src/broker/daemon.ts +++ b/src/broker/daemon.ts @@ -41,7 +41,18 @@ export async function ensureBroker(opts: { stateDir?: string } = {}): Promise") + .option("--fix", "repair issues (default behavior)") .option("--no-fix", "diagnose only, do not repair") .option("--json", "machine-readable output", false) .action(async (opts: { workspace?: string; fix: boolean; json: boolean }) => { @@ -1327,21 +1328,37 @@ brokerCmd else say("Broker is not running. Use `c2c broker start`."); return; } - const info = await adminFetch( - runtime, - "GET", - "/admin/info" - ); - if (opts.json) { - say(JSON.stringify({ ok: true, running: true, ...info })); - return; + try { + const info = await adminFetch( + runtime, + "GET", + "/admin/info" + ); + if (opts.json) { + say(JSON.stringify({ ok: true, running: true, ...info })); + return; + } + check(`Installation: ${info.installationId}`); + check(`Broker: running (port ${info.port})`); + if (info.tunnel.running && info.tunnel.url) check(`Connector URL: ${info.tunnel.url}/mcp`); + else say("· Public endpoint: not enabled"); + check(`Workspaces registered: ${info.workspaceCount ?? 0}`); + check(`Active Codex sessions: ${info.activeSessions ?? 0}`); + } catch (error) { + // The runtime file exists and matches this installation, but the probe + // failed — typical of a sandboxed agent blocking loopback requests, not + // a dead broker. Report that instead of claiming it is down. + const detail = + `runtime file says pid ${runtime.pid} on port ${runtime.port}, but the broker did not respond ` + + `(${(error as Error).message}). If this is a sandboxed agent, run the command outside the sandbox ` + + `or approve escalation.`; + if (opts.json) say(JSON.stringify({ ok: true, running: "unknown", probe: "failed", detail, runtime })); + else { + cross(`Broker state unclear: ${detail}`); + say(`· Connector URL (from runtime): ${runtime.publicUrl ? runtime.publicUrl + "/mcp" : "not recorded"}`); + } + process.exitCode = 1; } - check(`Installation: ${info.installationId}`); - check(`Broker: running (port ${info.port})`); - if (info.tunnel.running && info.tunnel.url) check(`Connector URL: ${info.tunnel.url}/mcp`); - else say("· Public endpoint: not enabled"); - check(`Workspaces registered: ${info.workspaceCount ?? 0}`); - check(`Active Codex sessions: ${info.activeSessions ?? 0}`); }); const brokerTunnelCmd = brokerCmd