Maintainer-approved P2 restructure. Principle: what a new user needs in the first
10 minutes stays in README; the operations manual moves to docs/. Content MOVED,
not rewritten — an independent verifier swept all 20 original sections, 60+
distinctive facts, and all table rows against the new corpus: zero content loss.
New files (verbatim moves + two mandated dedup merges):
- docs/lan-mode.md (396) — LAN setup, key management, quotas, anonymous access,
deployment/security model + honest limits, client connect
- docs/tui-mode.md (196) — full TUI section + the four giant env-cell essays as
prose subsections; opens with the single-user SECURITY
warning + the PAUSED billing-split status banner
- docs/troubleshooting.md (136) — full troubleshooting; canonical 401/credential-
isolation explanation (union of the 4 prior copies)
- docs/upgrading.md (79) — upgrade paths, snapshots, rollback, auto-sync
README keeps: pitch (byte-identical), new TOC, 62-line Quickstart, How It Works
verbatim (incl. #171 billing-status note + workload fit), the three release_kit-
pinned tables (Available Models / API Endpoints / all 37 Environment Variables
rows — 4 giant TUI cells now one-line pointers), All Commands, slim Troubleshooting
(bootstrap quirks retained per release_kit bootstrap_quirk_policy), summary stubs
linking each moved doc, Repository Layout (+4 doc rows), Governance, Support.
Dedup (canonical copies): sdk-cli vs subscription-pool table → docs/tui-mode.md;
credential-isolated-home / permanent-401 → docs/troubleshooting.md#tui-401.
Link retargets: docs/runbooks/615-canary.md, docs/runbooks/tui-flip-rollback.md,
setup.mjs (one banner string; node --check clean). 126 links across the touched
files verified resolving; repo-wide grep shows no reference to a removed anchor.
npm test: 332 passed / 0 failed.
Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>
6.4 KiB
Part of OCP — full troubleshooting manual. The README keeps a slim version with the most common issues and the one-time bootstrap quirks; everything else lives here.
Troubleshooting
The simplest path: ask your AI.
Paste this prompt:
Run `ocp doctor` and follow its `next_action`. Tell me if you hit
anything that needs human input.
The doctor produces a JSON next_action with ai_executable[] (commands
the agent runs verbatim) and human_required[] (steps that need you,
typically just OAuth).
Manual debugging
Setup fails with "claude: command not found"
setup.mjs requires the Claude CLI to be on PATH. Install it via the official guide, confirm with which claude, then run claude auth login before re-running node setup.mjs.
Setup fails with "EADDRINUSE: port 3456 already in use"
Something else is already bound to port 3456 — usually an old OCP instance. Check what:
lsof -nP -iTCP:3456 -sTCP:LISTEN
If it's an old OCP process, stop it before re-running setup:
launchctl bootout gui/$(id -u)/dev.ocp.proxy # macOS launchd
systemctl --user stop ocp-proxy # Linux systemd (installed as a --user unit)
(There is no ocp stop subcommand — the proxy runs as a service, so stopping it goes through the service manager above. ocp restart exists for the bounce case.)
Setup fails with "node: command not found" or version error
OCP requires Node.js 22.5+. Install:
brew install node # macOS
# Linux: see https://nodejs.org/en/download for current install commands
Confirm with node --version (should be ≥ v22.5).
Requests fail or agents stuck
# Clear sessions and restart
ocp clear
ocp restart
# If using OpenClaw gateway
openclaw gateway restart
Env var change (e.g. CLAUDE_BIND, CLAUDE_CODE_OAUTH_TOKEN) doesn't take effect after restart
On macOS, ocp restart does a full launchctl bootout + bootstrap of the agent, which re-reads the plist EnvironmentVariables — so an env change you made (in ~/Library/LaunchAgents/dev.ocp.proxy.plist) actually takes effect:
ocp restart
This is deliberate: the older launchctl kickstart -k only re-execs the process and reuses launchd's cached environment, so plist env edits would be silently ignored. If you ever restart the agent by hand, use bootout+bootstrap, not kickstart -k:
launchctl bootout gui/$(id -u)/dev.ocp.proxy 2>/dev/null
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.ocp.proxy.plist
Verify the new value reached the running process:
ps -E -p "$(launchctl print gui/$(id -u)/dev.ocp.proxy 2>/dev/null | awk '/pid =/{print $3}')" | tr ' ' '\n' | grep CLAUDE_
On Linux, systemctl --user restart already re-reads the unit's EnvironmentFile, so no special handling is needed.
Usage shows "unknown"
Usually caused by an expired Claude CLI session. Fix:
claude auth login
ocp restart
Startup log warns "OpenClaw registry out of sync"
On boot, OCP compares OpenClaw's registered models against models.json and warns if they drift. Cause: someone (or an OpenClaw upgrade) modified ~/.openclaw/openclaw.json and removed entries OCP expects. Fix:
node ~/ocp/scripts/sync-openclaw.mjs
This is read-only at startup; the warning never blocks the gateway from running.
A TUI session vanished right after upgrading OCP
If you ran a pre-3.21.1 OCP instance and a post-3.21.1 instance on the same host at the same time during an upgrade, the new instance's one-time boot reap can, once, kill an old-format (ocp-tui-<8hex>) live TUI session belonging to the still-running old instance — restart the affected session (ocp restart or re-run your TUI turn) and it will come back under the new instance's port-scoped naming.
OpenClaw shows old models after ocp update (v3.10→v3.11 only)
One-time bootstrap quirk for the v3.10.0 → v3.11.0 jump only — the running shell had the old cmd_update cached. Run once manually:
node ~/ocp/scripts/sync-openclaw.mjs
openclaw gateway restart # so OpenClaw re-reads the config
Future ocp update invocations sync automatically.
TUI-mode returns a permanent Please run /login 401 (re-login doesn't stick)
A long-running TUI-mode host can get stuck returning a permanent 401 (Please run /login · API Error: 401) that re-login cannot fix.
Root cause (two layers): interactive claude prefers ~/.claude/.credentials.json over the CLAUDE_CODE_OAUTH_TOKEN env var (this is unlike the -p path, where the env token wins). So (a) a stale/corrupt credentials.json shadows the env token — passing the token is not enough on its own; and (b) when claude does use credentials.json, its single-use OAuth refresh token can be corrupted (ending up an empty string) by the per-request spawn + kill-session teardown racing claude's token rotation. Re-login writes a fresh token, but the next spawn re-corrupts it. Proven live on PI231: env token passed + broken credentials.json present → 401; env token passed + credentials.json moved aside → works.
Fix: set CLAUDE_CODE_OAUTH_TOKEN on the OCP host and leave OCP_TUI_HOME unset. OCP then runs the TUI claude in a credential-isolated home ($HOME/.ocp-tui/home) that has no credentials.json at all, so the env token is the only credential (authoritative — nothing shadows it) and claude never runs the refresh path (so the single-use token can't be corrupted). Then restart — on systemd daemon-reload, on launchd bootout+bootstrap; kickstart -k does not reload env. Verify the env reached the process and the boot log shows the isolated home:
# Linux (systemd): confirm the token is in the service env
tr '\0' '\n' < /proc/$(pgrep -f server.mjs | head -1)/environ | grep CLAUDE_CODE_OAUTH_TOKEN
# Boot log should read: TUI-mode: ON home=$HOME/.ocp-tui/home ... auth=env-token (credential-isolated home — no credentials.json)
If you previously set
OCP_TUI_HOMEto the real home (or any home that contains acredentials.json), unset it so the credential-isolated default takes effect — otherwise the shadowingcredentials.jsonremains in play.
See Subscription-pool (TUI) mode and ADR 0007 PR-C / PR-D amendments.