The Control UI is a small Vite + Lit single-page app served by the Gateway:
http://<host>:18789/gateway.controlUi.basePath (e.g. /coderclaw)It speaks directly to the Gateway WebSocket on the same port.
If the Gateway is running on the same computer, open:
If the page fails to load, start the Gateway first: coderclaw gateway.
Auth is supplied during the WebSocket handshake via:
connect.params.auth.tokenconnect.params.auth.password
The dashboard settings panel lets you store a token; passwords are not persisted.
The onboarding wizard generates a gateway token by default, so paste it here on first connect.When you connect to the Control UI from a new browser or device, the Gateway
requires a one-time pairing approval — even if you’re on the same Tailnet
with gateway.auth.allowTailscale: true. This is a security measure to prevent
unauthorized access.
What you’ll see: “disconnected (1008): pairing required”
To approve the device:
# List pending requests
coderclaw devices list
# Approve by request ID
coderclaw devices approve <requestId>
Once approved, the device is remembered and won’t require re-approval unless
you revoke it with coderclaw devices revoke --device <id> --role <role>. See
Devices CLI for token rotation and revocation.
Notes:
127.0.0.1) are auto-approved.chat.history, chat.send, chat.abort, chat.inject)channels.status, web.login.*, config.patch)system-presence)sessions.list, sessions.patch)cron.*)skills.*)node.list)exec host=gateway/node (exec.approvals.*)~/.coderclaw/coderclaw.json (config.get, config.set)config.apply) and wake the last active sessionconfig.schema, including plugin + channel schemas); Raw JSON editor remains availablestatus, health, models.list)logs.tail)update.run) with a restart reportCron jobs panel notes:
delivery.mode = "webhook" with delivery.to set to a valid HTTP(S) webhook URL.cron.webhookToken to send a dedicated bearer token, if omitted the webhook is sent without an auth header.notify: true can still use cron.webhook until migrated.chat.send is non-blocking: it acks immediately with { runId, status: "started" } and the response streams via chat events.idempotencyKey returns { status: "in_flight" } while running, and { status: "ok" } after completion.chat.history responses are size-bounded for UI safety. When transcript entries are too large, Gateway may truncate long text fields, omit heavy metadata blocks, and replace oversized messages with a placeholder ([chat.history omitted: message too large]).chat.inject appends an assistant note to the session transcript and broadcasts a chat event for UI-only updates (no agent run, no channel delivery).chat.abort)/stop (or stop|esc|abort|wait|exit|interrupt) to abort out-of-bandchat.abort supports { sessionKey } (no runId) to abort all active runs for that sessionKeep the Gateway on loopback and let Tailscale Serve proxy it with HTTPS:
coderclaw gateway --tailscale serve
Open:
https://<magicdns>/ (or your configured gateway.controlUi.basePath)By default, Serve requests can authenticate via Tailscale identity headers
(tailscale-user-login) when gateway.auth.allowTailscale is true. CoderClaw
verifies the identity by resolving the x-forwarded-for address with
tailscale whois and matching it to the header, and only accepts these when the
request hits loopback with Tailscale’s x-forwarded-* headers. Set
gateway.auth.allowTailscale: false (or force gateway.auth.mode: "password")
if you want to require a token/password even for Serve traffic.
coderclaw gateway --bind tailnet --token "$(openssl rand -hex 32)"
Then open:
http://<tailscale-ip>:18789/ (or your configured gateway.controlUi.basePath)Paste the token into the UI settings (sent as connect.params.auth.token).
If you open the dashboard over plain HTTP (http://<lan-ip> or http://<tailscale-ip>),
the browser runs in a non-secure context and blocks WebCrypto. By default,
CoderClaw blocks Control UI connections without device identity.
Recommended fix: use HTTPS (Tailscale Serve) or open the UI locally:
https://<magicdns>/ (Serve)http://127.0.0.1:18789/ (on the gateway host)Downgrade example (token-only over HTTP):
{
gateway: {
controlUi: { allowInsecureAuth: true },
bind: "tailnet",
auth: { mode: "token", token: "replace-me" },
},
}
This disables device identity + pairing for the Control UI (even on HTTPS). Use only if you trust the network.
See Tailscale for HTTPS setup guidance.
The Gateway serves static files from dist/control-ui. Build them with:
pnpm ui:build # auto-installs UI deps on first run
Optional absolute base (when you want fixed asset URLs):
CODERCLAW_CONTROL_UI_BASE_PATH=/coderclaw/ pnpm ui:build
For local development (separate dev server):
pnpm ui:dev # auto-installs UI deps on first run
Then point the UI at your Gateway WS URL (e.g. ws://127.0.0.1:18789).
The Control UI is static files; the WebSocket target is configurable and can be different from the HTTP origin. This is handy when you want the Vite dev server locally but the Gateway runs elsewhere.
pnpm ui:devhttp://localhost:5173/?gatewayUrl=ws://<gateway-host>:18789
Optional one-time auth (if needed):
http://localhost:5173/?gatewayUrl=wss://<gateway-host>:18789&token=<gateway-token>
Notes:
gatewayUrl is stored in localStorage after load and removed from the URL.token is stored in localStorage; password is kept in memory only.gatewayUrl is set, the UI does not fall back to config or environment credentials.
Provide token (or password) explicitly. Missing explicit credentials is an error.wss:// when the Gateway is behind TLS (Tailscale Serve, HTTPS proxy, etc.).gatewayUrl is only accepted in a top-level window (not embedded) to prevent clickjacking.pnpm ui:dev to a remote Gateway), add the UI
origin to gateway.controlUi.allowedOrigins.Example:
{
gateway: {
controlUi: {
allowedOrigins: ["http://localhost:5173"],
},
},
}
Remote access setup details: Remote access.