Repository navigation
Description
Description
The Desktop app's embedded server (sidecar) is only reachable by the app's own renderer. Companion tools running on the same machine (status-bar widgets, CLI wrappers, IDE extensions, automation scripts) cannot discover or authenticate to it, because the server URL/credentials are generated in memory and never persisted.
Currently the only working "API" for external tools is scraping main.log for the URL:
[info] server ready { url: 'http://127.0.0.1:62057' }
…but the log never contains the password, so discovery always ends in HTTP 401.
Current behavior (v1.18.26, macOS)
-
out/main/index.js:const password = randomUUID();— a fresh password per launch, passed to the sidecar viapostMessage, never written to disk. -
out/main/sidecar.js→prepareSidecarEnv()unconditionally overwrites the environment:Object.assign(process.env, { OPENCODE_SERVER_USERNAME: "opencode", OPENCODE_SERVER_PASSWORD: password, // always the IPC-provided value ... })
so externally fixing the password (e.g.
launchctl setenv OPENCODE_SERVER_PASSWORD ...) has no effect. -
The password is only visible inside processes the server itself spawns (session tool processes). Tools outside that process tree cannot read it:
ps -Eshows the kernel-initial env, and the runtime-injected value is not there.
Result: every desktop restart rotates port + password, and each external tool has to implement a hack — e.g. running a "bridge" script inside an opencode session that dumps its own env to a well-known file — which only works after a session has started, and silently breaks again on the next restart.
Proposal
After server ready, persist the endpoint once, atomically, with 0600 perms, e.g.:
// $XDG_STATE_HOME/opencode/server.json (sidecar already sets XDG_STATE_HOME)
{
"url": "http://127.0.0.1:62057",
"username": "opencode",
"password": "<uuid>",
"pid": 52431,
"writtenAt": 1788490335685
}- Rewrite it on every (re)start; remove or invalidate it on clean shutdown.
- Loopback-only listener + same-user-readable file puts it in the same trust domain as the existing env-var mechanism used by the CLI (
OPENCODE_SERVER_PASSWORD), so no new attack surface.
(Alternative, also fine: let the desktop honor an externally provided OPENCODE_SERVER_PASSWORD instead of always generating one. Then tools can pin credentials via launchctl setenv and discover the port from main.log/lsof as they already do.)
Precedent inside the same codebase
The v2 CLI background-service path already treats credentials as externally retrievable:
const url = await run(binary, ["service", "start"], ...)
const password = await run(binary, ["service", "get", "password"], ...)The desktop sidecar is currently the only server mode whose credentials are unreachable from outside the process tree.
Alternatives considered (and why they don't work today)
- A plugin that writes credentials on startup — natural place for it, but desktop plugin loading is broken/limited on 1.18.x (see [Bug] Production Desktop 1.18.15 still cannot load Bun-target plugins (oh-my-openagent); V2 sidecar not bundled #41033, Desktop app: local plugins load and register but their hooks (tool.execute.before, event) are never invoked #38604, [Desktop][Windows] npm plugin from config is silently never loaded — no install attempt logged, docs/cache mismatch #44367), so it can't be relied on.
- Scraping
ps -EforOPENCODE_SERVER_PASSWORD— the value is injected at runtime, so it only appears in short-lived session tool processes; not a stable discovery channel.
Persisting the endpoint (or honoring an external password) would let the growing ecosystem of companion tools connect zero-touch, instead of every tool shipping its own rebridge hack.
Environment
- OpenCode Desktop 1.18.26, macOS 15 (arm64)
- Reproduction: start desktop →
curl -u opencode:anything http://127.0.0.1:<sidecar-port>/global/health→ 401, with no supported way to learn the real password from outside the app.
Activity
neriousy commentedon Oct 19, 2026
In v2 the desktop app no longer runs a private sidecar with a per-launch password; it starts or reuses the shared background server, which writes its URL, pid, version and password to
~/.local/state/opencode/service.json(or$XDG_STATE_HOME/opencode/service.jsonwhen that is set) with 0600 permissions. Companion tools can read that file, or runopencode service statusandopencode service get password, and the password stays the same across restarts (opencode service set password <value>pins your own). This is available since v2.0.0. To get v2:npm i -g @opencode/cli@latestorcurl -fsSL https://opencode.ai/v2/install | bash(desktop: update the app). If you still see this on v2, please open a new issue with the details.