Packaged desktop showed no sessions in 1.12.4. Root cause: the sanitized
session-list proxy path added in #1538 forwarded the renderer's
"authorization" header (the OpenChamber UI client token) to the managed
OpenCode upstream alongside the managed "Authorization" credential.
OpenCode does not recognize UI client tokens, so every session-list
request answered 401 — only in the packaged app, because only its
renderer (openchamber-ui:// origin) attaches a bearer token; dev web and
dev Electron run same-origin without one. The legacy http-proxy path
overwrote the header correctly, which is why everything except session
lists kept working.
Proxy fix:
- proxy-headers: filter the client "authorization" header out of
forwarded request headers; the OpenCode upstream must only ever see
its own managed credentials. Covered by tests.
Desktop cwd:
- electron: launch the managed OpenCode CLI from the user home instead
of app userData, matching upstream desktop behavior. userData-as-cwd
made OpenCode treat the app-data folder as a separate empty workspace.
Home directory poisoning loop:
- directoryPersistence: stop replaying localStorage homeDirectory
through synchronizeHomeDirectory on boot/auth resync. The persisted
value is only a boot-time cache; replaying it re-wrote stale values
(e.g. a project path) into desktop settings on every start, overriding
the authoritative /api/fs/home resolution.
- persistence: never overwrite an injected window.__OPENCHAMBER_HOME__
with a persisted value.
- useDirectoryStore: host switches happen in place (no reload), so
re-resolve home from the new runtime's /api/fs/home on endpoint
change instead of keeping the previous host's value.
- opencode client: only short-circuit to the injected desktop home when
the active runtime is local; remote runtimes ask /api/fs/home.
Settings hygiene:
- persistSettings: log field names only — change payloads can carry
credentials (UI password, client tokens, tunnel tokens) that must not
reach the log file; drop step-by-step log chatter.
- validateProjectEntries: only stat project paths when the incoming
update actually touches the projects list, not on every settings save.
- remove the write-only approvedDirectories setting everywhere and add
a migration that strips the stale key from persisted settings.
Tests:
- usePluginsStore.test: register an own runtime-fetch module mock so the
suite is independent of process-global mock.module leakage from other
files, and restore globalThis.fetch after the suite.
- persistence.test: clean up the window global created for the suite.
openchamber --port 8080# Custom port
openchamber --lan --port 3000# Listen on LAN (0.0.0.0)
openchamber --ui-password secret # Password-protect UI
openchamber startup enable# Start at login as a native serviceOPENCHAMBER_UI_PASSWORD=secret openchamber startup enable# Save service password env
openchamber startup status # Show startup service status
openchamber startup disable # Remove startup service
openchamber tunnel help# Tunnel lifecycle commands
openchamber tunnel providers # Show provider capabilities
openchamber tunnel profile add --provider cloudflare --mode managed-remote --name prod-main --hostname app.example.com --token <token>
openchamber tunnel start --profile prod-main
openchamber tunnel start --provider cloudflare --mode quick --qr
openchamber tunnel start --provider cloudflare --mode managed-local --config ~/.cloudflared/config.yml
openchamber tunnel status --all # Show tunnel state across instances
openchamber tunnel stop --port 3000# Stop tunnel only (server stays running)
openchamber connect-url --port 3000# Add this server to OpenChamber Desktop
openchamber connect-url --server http://host:3000 --qr
openchamber connect-url --port 3000 --qr
openchamber logs # Follow latest instance logsOPENCODE_PORT=4096OPENCODE_SKIP_START=true openchamber # Connect to external OpenCode serverOPENCODE_HOST=https://myhost:4096 OPENCODE_SKIP_START=true openchamber # Connect via custom host/HTTPS
openchamber stop # Stop server
openchamber update # Update to latest
startup enable snapshots your current environment into the native service so startup behaves like you launched openchamber from the same shell. This preserves provider tokens, PATH, SSH agent settings, and other CLI auth/config env vars. Use --no-env-snapshot if you want a minimal service env.
This runs OpenChamber as an API-only server without the desktop app or browser UI assets on that machine, then creates a link for Desktop to import. --lan makes the server reachable from other machines. --server is the address Desktop should use.
When OpenChamber was started with --lan or --host 0.0.0.0, connect-url automatically uses a detected LAN IP instead of 127.0.0.1. Use --server http://host:3000 to override the advertised address, and include --lan when connect-url needs to start the server for LAN access.
Paste the printed openchamber://connect?... link in Desktop under Settings -> Remote Instances -> Direct Instances -> Import Link. The link contains the server URL and a client token. It does not enable browser UI password protection; use --ui-password when exposing a server beyond localhost.
systemd service (VPN / LAN access)
Run OpenChamber and OpenCode as separate persistent services — useful when you want to access your
dev machine over a VPN (e.g. Tailscale) or LAN without a Cloudflare tunnel.
How it works:
OpenCode runs as its own service, binding only to localhost.
OpenChamber connects to it via OPENCODE_HOST and --lan makes it reachable on your VPN IP.
--foreground keeps the CLI process alive so systemd can track and restart it.
Why set PATH and SSH_AUTH_SOCK?
systemd user services start with a minimal environment — no shell profile is sourced.
Without an explicit PATH, OpenCode won't find tools installed via Homebrew, npm, or ~/.local/bin.
Without SSH_AUTH_SOCK, git operations over SSH (push, pull, clone) will fail because the agent socket isn't inherited.
Adjust the PATH to match your own tool installation paths.
%t expands to $XDG_RUNTIME_DIR (e.g. /run/user/1000), where most SSH agents write their socket.
OpenChamber will be reachable at http://<your-vpn-hostname>:3000 from any device on your VPN.
Note:--host 0.0.0.0 is required to listen on all interfaces. The default
bind address is 127.0.0.1 (localhost only). Use --host <ip> or
OPENCHAMBER_HOST=<ip> to bind to a specific interface instead.
Managed-local path note: OPENCHAMBER_TUNNEL_CONFIG must point to a path inside the container user home (/home/openchamber/...). If your Cloudflare config references a credentials JSON file, that file path must also be accessible inside the container (mount with volumes).
Website docs source lives at packages/docs/content/docs/reverse-proxy.mdx.
Tunnel behavior notes
OpenChamber supports one active tunnel per running instance (port).
Starting a tunnel with a different mode/provider on the same instance replaces the current tunnel.
Replacing or stopping a tunnel revokes existing connect links and invalidates remote tunnel sessions for that instance.
Connect links are one-time tokens; generating a new link revokes the previous unused link.
Data Directory Permission Note: The data/ directory is mounted into the container for persistent storage (config, sessions, SSH keys, workspaces). Before running, ensure the directory exists and has proper permissions:
My wife, who - with zero AI background - sat down with the app for the first time and built the firework celebration that plays on every successful push.
Every contributor who shaped this project with their PRs, ideas, and attention to detail.