X056 Gateway — Access Guide
What this is
The x056 gateway is a self-hosted "remote control" for Claude Code with automatic
failover between two Claude Max accounts. A supervisor process drives headless
claude -p --output-format stream-json sessions and, when the active account
hits its usage limit, respawns claude -p --resume <session-id> under the
other account's CLAUDE_CONFIG_DIR (the two accounts share the same
projects/ tree), so one continuous session survives the switch. It exposes a
small HTTP API plus a single-page control panel, both protected by a bearer
token, so you can drive Claude Code sessions from a browser or script on any
machine that can reach the host.
It runs as a single Docker Compose service (x056) built from the
Dockerfile in this repo, listening on port 4056.
Running it
Build and start (or rebuild after a code change):
docker compose up -d --build
Check status / logs:
docker compose ps
docker compose logs -f x056
Stop:
docker compose down
First-time setup: the access token
The gateway refuses to start without X056_TOKEN set in the environment.
Generate one into a local .env file (never commit this file — it's in
.gitignore):
echo "X056_TOKEN=$(openssl rand -hex 32)" > .env
docker compose up -d --build
Compose reads .env automatically and injects X056_TOKEN into the
container. Keep the value handy — it's the only credential you need to use
the gateway.
Using the panel
Browse to http://<server-ip>:4056/ and paste the token when prompted. The
panel talks to the same API described below, over the same port.
GET /healthz is unauthenticated and returns {"ok":true} once the process
is up — useful for container healthchecks and quick liveness checks.
API surface
All /api/* routes require the token, either as a bearer header or a token
query parameter (handy for the SSE stream, where setting headers is awkward
from a browser EventSource):
Authorization: Bearer <token>
or
?token=<token>
Examples (replace $TOKEN with the value from .env):
Start a new session:
curl -sS -X POST localhost:4056/api/sessions \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"prompt": "list files in this repo", "cwd": "/home/efran/remote-development/x056-remote-control"}'
Continue the current session with another message:
curl -sS -X POST localhost:4056/api/sessions/current/messages \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"prompt": "now summarize what you found"}'
Stream events for the current session (Server-Sent Events):
curl -N "localhost:4056/api/sessions/current/stream?token=$TOKEN"
List known sessions:
curl -sS -H "Authorization: Bearer $TOKEN" localhost:4056/api/sessions
List accounts and their live quota/state:
curl -sS -H "Authorization: Bearer $TOKEN" localhost:4056/api/accounts
Force an account switch (e.g. to test failover, or recover from a stuck
session):
curl -sS -X POST -H "Authorization: Bearer $TOKEN" localhost:4056/api/switch
State locations
- Account config dirs (host paths, bind-mounted read/write into the
container at the same path):~/.claude-x056-aand~/.claude-x056-b.
These hold each Claude Max account's credentials and Claude Code config —
nevercator otherwise print their contents. - Session transcripts: under each account's config dir, e.g.
~/.claude-x056-a/projects/...— since both accounts share the same
CLAUDE_CONFIG_DIR-relativeprojects/layout, a resumed session's
transcript is visible to whichever account is currently active. - Gateway state (account registry, session bookkeeping): the named Docker
volumex056-state, mounted at/app/statein the container. This starts
empty on a fresh deployment —/api/accountsreturns a controlled error
until the setup step that populatesstate/accounts.jsonhas been run. - Workspace: the host's
~/remote-developmenttree is bind-mounted at the
same path inside the container (X056_WORKSPACE_ROOT), so sessions can
operate on real project directories.
Security
The bearer token is the only lock on what is otherwise a plaintext HTTP
port exposed to the network. Treat it like a password: don't commit it,
don't log it, don't share it outside a secure channel. Recommended
hardening for anything beyond a trusted LAN:
- UFW allowlist — restrict inbound
4056/tcpto known source IPs:
bash sudo ufw allow from <trusted-ip> to any port 4056 proto tcp - Tailscale — bind the published port to the host's Tailscale interface
address instead of all interfaces, so the service is only reachable over
the tailnet:
```yaml
ports:- "100.x.y.z:4056:4056"
```
- "100.x.y.z:4056:4056"
Token rotation: edit .env with a new value, then re-apply:
echo "X056_TOKEN=$(openssl rand -hex 32)" > .env
docker compose up -d
Compose will recreate the container with the new token; any previously
issued token stops working immediately.
SSE token caveat: the stream endpoint authenticates via
?token=(EventSource cannot send headers). If you ever put a reverse proxy in front, that token will appear in its access logs — scrub or disable query-string logging there, or stick to Tailscale-only exposure.
Adopting an interactive session
Sessions from your regular claude (under ~/.claude) are invisible to the gateway by design. To hand one over, run on the host, from the session's project directory:
npm run x056 -- adopt <session-id>
This copies the transcript into the failover tree, updates the CLI state, and — if a gateway is running with .env present — points the panel's current session at it. The panel renders the adopted history on next load. Note it's a fork-by-copy: after adopting, continue in ONE place (panel or terminal), not both.
Self-deploy from inside a session (request/actuate split)
The container can never touch the Docker daemon (deliberate — a mounted socket would be root-equivalent on the host). Instead, a host-side actuator runs the one fixed command when asked:
- One-time install on the host:
bash scripts/install-deployer.sh(adds a per-minute cron entry, no sudo). - Anyone with repo write access — including Claude working inside the container — triggers a deploy with
touch .deploy/requested. - The actuator waits until no session is running (never kills an in-flight turn), then runs
docker compose up -d --build, logs to.deploy/last.log, and records.deploy/status.json.
Worst-case abuse from a compromised container is a rebuild of itself — not host command execution.