Releases: dtzp555-max/ocp
v3.21.0
v3.21.0 — 2026-06-25
Cleanup + docs release: TUI dead-code removal, docs honesty, and release prep. No new cli.js wire behavior; the default path (CLAUDE_TUI_MODE unset) is byte-for-byte unchanged.
TUI dead-code / footgun cleanup
- A1 — removed inert entrypoint-env path (
lib/tui/session.mjs): deletedresolveTuiEntrypointEnv()and the redundant env-strip block inrunTuiTurn. The{env}object passed tospawnSync(tmux itself) was the wrong target — tmux does NOT forward the spawning process's environment to the pane; the pane'sclaudegets its env exclusively from theenvprefix string built insidebuildTuiCmd(verified live 2026-06-01). The spawnSync env is now intentionally minimal (HOMEonly). Behavior is unchanged:buildTuiCmdalready handled all claude-specific env vars via its prefix string. - A2 — removed test-only transcript helpers (
lib/tui/transcript.mjs): deletedencodeCwd()andtranscriptPath()exports and the tests that pinned them. Production resolves transcripts exclusively viafindTranscriptPath()(glob by session-id), which is immune to the exact path-encoding rule. No non-test importers existed (grep confirms). A// TODOcomment nearfindTranscriptPath()notes that a CI fixture-contract test would make claude-schema drift fail loudly. - A3 — removed headless-unusable
--dangerously-skip-permissionsbranch (lib/tui/session.mjs+README.md):OCP_TUI_FULL_TOOLS=1now always takes the--allowedToolspath. The removed branch pushed--dangerously-skip-permissionswhenCLAUDE_SKIP_PERMISSIONS=true; on claude v2.1.x this triggers an interactive bypass-acceptance screen that a headless tmux pane cannot answer → the turn hangs to the wallclock cap and bricks the pane. The working path is--allowedTools+ scratch-homesettings.jsonadditionalDirectories.CLAUDE_SKIP_PERMISSIONSfor the-ppath is unchanged (still used inserver.mjs).
Docs
- Client-tools boundary (README
§ How It Works): OCP is a text-prompt bridge only — it does not pass OpenAItools/functionsor Anthropictool_useblocks to the client. Clients receive assistant TEXT only; client-local tool execution is not supported by design (bypassingcli.js= out of scope perALIGNMENT.md). - ToS honesty (README
§ Deployment model & security): pooling one Claude subscription across multiple distinct people may violate Anthropic's Consumer ToS and risk account suspension by the abuse classifier. The defensible framing is "one person, your own devices" — friends/team sharing is not. The prior language ("account terms are your call") was accurate but understated the risk. - "Why OCP" posture (README
§ Why OCP?): new bullet making explicit that OCP drives the officialclaudeCLI as-is — no OAuth token extraction, no binary patching, no protocol invention — so traffic looks like genuine Claude Code (cc_entrypoint=cli). - Promotion plan (
docs/PROMOTION.md): "stable & visible" strategy covering goal (polish + low-key OSS visibility, NOT growth-hacking given the live ToS/billing risk), pre-requisites (stability first), honest ToS disclosure requirement, items explicitly skipped (multi-backend routing → OLP; gateway model-discovery; raw API passthrough → ALIGNMENT.md scope), TUI toggle as billing-split insurance, and low-key visibility actions. Framed as a recommendation for the maintainer to review, not a committed plan.
Previously shipped (v3.20.x) — documented here for completeness
- Default
-pspawn-home isolation (v3.20.0 / PR-A): per-requestclaudespawns run in a credential-free minimal scratch HOME ($HOME/.ocp/spawn-home, no.credentials.json/settings.json/plugins) with a neutral cwd and the env token, cutting per-request latency (measured ~10–28s → ~3–7s). Kill-switch:OCP_SPAWN_REAL_HOME=1. Active mode shown at startup and on/health.spawn. - Bounded concurrency wait-queue (v3.20.0 / PR-B): excess
-prequests queue (up toCLAUDE_MAX_QUEUE, default 16) instead of being rejected; a full queue returnsHTTP 429+Retry-After(not an opaque 500). New env vars:CLAUDE_MAX_QUEUE,CLAUDE_QUEUE_RETRY_AFTER. Surfaced on/health.concurrency+/health.stats.queueRejections. ocp restartmacOSbootout+bootstrap(v3.20.0 / PR-B): safe restart command that forces launchd to re-read the plist (unlikekickstart -kwhich reuses the cached env)./ocpplugin OpenClaw-2026.5.27 compat (v3.20.0 / PR-C): gateway plugin updated for the current OpenClaw API version.
v3.20.1
v3.20.1 — 2026-06-13
TUI-mode auth hardening: fixes the recurring Please run /login · API Error: 401 (the PI231 incident) and reaps leaked defunct claude sessions. (#141)
Fixed
- TUI 401 / credential corruption (#141) — interactive
claudeprefers~/.claude/.credentials.jsonover theCLAUDE_CODE_OAUTH_TOKENenv var (unlike-pmode, where the env token wins). OCP TUI's per-request spawn +kill-sessioncycle raced claude's single-use refresh-token rotation, corrupting the refresh token to an empty string → permanent 401 thatclaude /logincouldn't fix (each new spawn re-corrupted it). This bit Linux/file-based hosts specifically (macOS reads credentials from the Keychain, so Mac mini was immune). Fix: whenCLAUDE_CODE_OAUTH_TOKENis set, the TUI claude now runs in a credential-free scratch HOME (<HOME>/.ocp-tui/home, overridable byOCP_TUI_HOME) seeded with onboarding + cwd-trust but no.credentials.json, so the env token is the only credential and claude never runs the refresh path. Recurrence-proof — a laterclaude logincan no longer break TUI. Also:buildTuiCmdpassesCLAUDE_CODE_OAUTH_TOKENto the spawn, andreapStaleTuiSessionsreaps defunctclaudesessions (tmux-server-owned zombies) viakill-serverwhen no foreign session remains, plus a 15-min idle-gated periodic reap. When the env token is unset, behaviour is byte-for-byte unchanged (real-home + credentials.json). Two independent fresh-context reviewers (Iron Rule 10) + a live PI231 portability test (works with a corrupt credentials.json present). Authorized by the ADR 0007 PR-D amendment (Class B).
Environment variables
CLAUDE_CODE_OAUTH_TOKEN— when set on a TUI host, TUI authenticates via this long-lived token in a credential-isolated home (recommended; immune to credentials.json corruption).OCP_TUI_HOME— overrides the TUI scratch home; if you previously pointed it at your real home, unset it to get the credential-isolated default.
v3.20.0
v3.20.0 — 2026-06-10
TUI-mode billing-safety hardening for the 2026-06-15 Anthropic billing split. A 5-dimension multi-agent audit (adversarial verification + live tests on all three hosts — PI231 / Oracle / Mac mini, claude 2.1.104 / 2.1.114 / 2.1.170) found the TUI subscription-pool path could silently bill the metered Agent SDK pool or poison the cache under realistic failure modes. Three PRs, each with a fresh-context reviewer (Iron Rule 10) and CI; the default path (CLAUDE_TUI_MODE unset) is byte-for-byte unchanged.
TUI — honesty & cache correctness (#137)
- C-1 —
callClaudeTuinow throws on a claude-CLI auth-failure banner (e.g.Please run /login · API Error: 401 …,Failed to authenticate. API Error: 401 …) instead of returning it as a real answer, so it is never cached, singleflight-shared, or counted as a model success. Conservative detector (whole trimmed text ≤100 chars +API Error: 4xx+ auth keyword + no code/quote char); overridable viaCLAUDE_TUI_ERROR_PATTERNS. Live-reproduced on PI231. - C-2 —
readTuiTranscriptdistinguishes a complete turn from a wallclock-truncated partial (truncatedflag);callClaudeTuithrowstui_wallclock_truncatedso a partial is never cached or counted as success. - C-3 —
verifyEntrypointreads theentrypointfield from any transcript line, not just{system, turn_duration}— some claude builds emit zero turn_duration lines (live-confirmed on Oracle's claude 2.1.114), which previously left the billing-drift assertion blind on those builds. - C-4 (paste) — short prompts (e.g.
hi) could never pass paste-landing detection; threshold lowered. Live-reproduced on PI231.
TUI — concurrency & observability (#139)
- Concurrency —
OCP_TUI_MAX_CONCURRENT(default 2) bounds concurrent interactiveclaudeboots via a queuing semaphore (lib/tui/semaphore.mjs); the slot is released on throw so honesty-gate / spawn failures never leak it; bounded wait-queue →tui_queue_full(503). Independent of the globalMAX_CONCURRENT(8) — a TUI turn is a heavy per-request cold-boot of tmux+claude + up to 120s wallclock. - Observability — additive
/healthtuiblock (enabled/entrypointMode/lastEntrypoint/entrypointMismatches/inflight/maxConcurrent) so an operator can poll for a silentsdk-climetered-pool drift (the audit's top risk) instead of grepping journald. Authorized by the ADR 0007 PR-B amendment under the ALIGNMENT grandfather provision (additive, behaviour-preserving — every pre-existing/healthfield unchanged).
Operations (#138)
docs/runbooks/615-canary.md— the 2026-06-15 credit-balance canary: quiesce, read the Agent SDK credit balance (manual — no programmatic API exists for that pool; OCP's/usageheaders are subscription rate-limit data, not the credit pool), one TUI canary turn, confirmentrypoint:cliin the transcript, green/red decision tree, periodic auto-mode self-classification mini-canary.docs/runbooks/tui-flip-rollback.md— flip/rollback per deployment (systemddaemon-reload; launchdbootout/bootstrap, notkickstart -k).setup.mjsauth quick-test gated behindOCP_SKIP_AUTH_TEST=1(theclaude -pprobe draws from the metered Agent SDK pool after 6/15).
New environment variables
v3.19.0
v3.19.0 — 2026-06-02
TUI-mode reliability + proxy-purity release. Two fixes diagnosed and verified live on both test hosts (PI231 / Oracle, claude 2.1.104 / 2.1.114), each its own PR with a fresh-context reviewer (Iron Rule 10), then an adversarial multi-host test battery (0 hangs / 0 crashes / 0 injection / 0 leaks). The default path (CLAUDE_TUI_MODE unset) is byte-for-byte unchanged.
TUI
- #130 — Fixed the "stuck typing" hang on large multi-line prompts. Three root causes: (1) terminal-turn detection only recognized
{system, turn_duration}, which older claude builds (e.g. 2.1.114) don't emit → the reader ran to the wallclock and returned partial text; now also accepts anassistantline with a finalstop_reason(end_turn/stop_sequence/max_tokens), whiletool_usestays non-terminal. (2) Large prompts pasted viasend-keys -ldelivered embedded newlines as separate Enter events → the prompt never landed; now usestmux load-buffer+paste-buffer -p(bracketed paste, atomic). (3) The paste-landed check false-positived on claude's empty curly-quote placeholder → Enter fired into an empty box; now positive-signal-only ([Pasted text]/ prompt text) with a readiness/paste-verify poll + fast-fail (deterministic ~5s error instead of a 120s wallclock hang). - #4 — TUI-mode never injects the host's
CLAUDE.md/ auto-memory into proxied turns. OCP is a proxy: the proxied client (OpenClaw / an IDE) owns its own context and memory.buildTuiCmdnow always setsCLAUDE_CODE_DISABLE_CLAUDE_MDS+CLAUDE_CODE_DISABLE_AUTO_MEMORY(unconditional — proxy purity is not an opt-in). Verified live with a markerCLAUDE.md: obeyed by the proxied turn before the fix, blocked after, on both hosts. Residual host-context vectors (managed-policy /settings.json/ output-styles) tracked in #133. The env is delivered via anenv-prefix on the tmux pane command (tmux does not forward the spawning process's environment, andnew-session -erequires tmux ≥3.2 while the cloud host runs 2.7).
v3.18.0
v3.18.0 — 2026-06-01
Hardening release from a multi-agent code audit (1 P0 + 14 P2 + 2 P3 findings, each adversarially verified and independently reviewed) plus three follow-ups (#123–#125). Every change shipped as its own PR with a fresh-context reviewer (Iron Rule 10). The single-user default path (AUTH_MODE=none, no TUI) is behavior-identical except the /health change in #109.
Security
- #109 (P0) —
/healthno longer advertisesPROXY_ANONYMOUS_KEYto remote callers by default. TheanonymousKeyfield is gated behind a newPROXY_ADVERTISE_ANON_KEY=1opt-in env var; localhost callers are always exempt. Prevents any LAN-reachable device from harvesting a working, quota-spending bearer credential from the unauthenticated/healthendpoint. Behavior change:ocp-connectzero-config Path A now requires the server to setPROXY_ADVERTISE_ANON_KEY=1; otherwise pass--keyor use anonymous access. - #114 — Dashboard escapes all DB-sourced strings (key names, usage rows) before
innerHTML; the revoke button uses adata-attribute + listener instead of an inlineonclicka quote could break out of;POST /api/keysvalidates key names server-side ([A-Za-z0-9 ._-]{1,64}). - #124 — Dashboard status/plan summary cards escaped too (uniform defense-in-depth over all
innerHTMLsinks). - #111 — Streaming error paths strip filesystem paths from claude error text / stderr before sending them to clients (
sanitizeError), matching the non-streaming path.
Reliability / correctness
- #110 — Non-array
messagesis rejected with a 400 (was silently hanging the connection until socket timeout); OpenAI arraycontentis flattened into the prompt instead of dumped as raw JSON; a streamed upstream error now emits an SSEerrorframe instead of a success-lookingfinish_reason:"stop". - #111 —
res.on("close")escalates SIGTERM→SIGKILL on client disconnect (closes a narrow re-occurrence of the #37 concurrency-slot leak on the hottest exit path);overallTimeris cleared on semantic completion so a slow-exiting child can't record a spurious post-success timeout; per-key quota is documented as best-effort (bounded overshoot ≤MAX_CONCURRENT, cache hits uncounted). - #113 — CLI/installer hardening:
ocp-pluginrestart uses the live uid +dev.ocp.proxy/ocp-proxylabels and drops the unsafepkillfallback;ocp-connectquotes +chmod 600s the persisted key;setup.mjsXML-escapes and newline-validates injected service-unit secrets.
Alignment / governance
- #112 — OAuth token-refresh host (
platform.claude.com/v1/oauth/token) re-verified against the compiled cli.js v2.1.154 (strings, no live probe) and recorded inALIGNMENT.md; usage-probe and default request model now derive frommodels.json(ADR 0003 SPOT) instead of hardcoded IDs. - #123 — The legacy
console.anthropic.com/v1/oauth/tokenhost is pinned in thealignment.ymlblacklist so a future OAuth-host drift hard-fails CI; the blacklist now documents its dual purpose (known hallucinations + pinned wrong-host variants of a verified Class A endpoint).
TUI
- #115 — The TUI LAN gate refuses any non-loopback bind (not just literal
0.0.0.0); the achievedcc_entrypointis asserted each turn and atui_entrypoint_mismatchwarning is logged on a silent degrade to the metered sdk-cli pool.
Refactor
- #125 —
isLoopbackBindextracted tolib/net.mjs, shared byserver.mjsand the test suite (was duplicated via a copy-paste mirror).
New environment variables
PROXY_ADVERTISE_ANON_KEY— opt-in (default off); advertisePROXY_ANONYMOUS_KEYon the public/healthbody for remote zero-config discovery (#109).
v3.17.1
v3.17.1 — 2026-05-31
Fix — code-audit P1/P2 hardening
Fixes from a multi-agent code audit (3 P1 + 5 P2, adversarially verified). The single-user default path (AUTH_MODE=none, no TUI) is behavior-identical.
Availability / correctness (P1):
- Guard
proc.stdinagainst EPIPE — a fast-failing spawnedclaude(auth error, bad model, large prompt) no longer crashes the single-process daemon. - Add
unhandledRejection/uncaughtException/clientErrorsafety nets + wrap all request-body read loops — a client aborting mid-upload no longer crashes the daemon. - TUI transcript reader: only
turn_durationis terminal (was alsotool_use), which silently truncated any TUI turn that used a built-in tool.
Security gates / cache integrity (P2):
AUTH_MODE=multi: the default spawn now passes--disallowedTools(Bash/Read/Write/Edit/…) so a guest prompt cannot drive operator-filesystem tools. Single-user path unchanged./sessions(DELETE),/settings(PATCH),/logs,/usage,/statusare now admin-gated (were dispatched before the admin check).- Streaming path no longer caches an
is_errorresponse as success (cache-poisoning fix). - TUI fail-loud guard extended to
none+0.0.0.0(unlessOCP_TUI_ALLOW_LAN=1) and+ PROXY_ANONYMOUS_KEY. - TUI
send-keyspaste uses-l(literal) so a prompt equal to a tmux key token (e.g.C-c) is typed, not interpreted.
v3.17.0
v3.17.0 — 2026-05-31
Provider — default claude invocation ported to stream-json + --system-prompt (Phase 6c)
OCP's default (non-TUI) claude spawn moves from claude -p --output-format text to claude --output-format stream-json --verbose --no-session-persistence --system-prompt <wrapper> (no -p). The NDJSON event stream is parsed into the assembled response. Benefits: ~64% per-request cost reduction and anti-hallucination via --system-prompt tool-use suppression. Clients see no API change — the OpenAI-compatible request/response shapes are identical. Faithful port of OLP's production-verified implementation; covered by 17 new stream-json parser tests.
cc_entrypoint=sdk-cli and bills against the Agent SDK credit pool. Use the new opt-in CLAUDE_TUI_MODE (below) to keep traffic on the Pro/Max subscription pool.
feat(tui): opt-in CLAUDE_TUI_MODE — serve via interactive claude (cc_entrypoint=cli / subscription pool), single-user only; default stream-json path unchanged
From 2026-06-15 Anthropic routes claude -p / --output-format invocations to the Agent SDK credit pool (cc_entrypoint=sdk-cli). This feature adds an opt-in bridge: when CLAUDE_TUI_MODE=true, OCP serves each request via a real interactive claude session (no -p, no --output-format) so it carries cc_entrypoint=cli and bills against the Pro/Max subscription.
The complete string response is read from claude's native JSONL session transcript and replayed to callers as a normal OpenAI completion or chunked SSE. Clients see no API change. The default stream-json path is byte-for-byte unchanged when CLAUDE_TUI_MODE is unset.
Security: single-user / single-operator only. Never enable on a multi-user OCP. See ADR 0007 and README § "Subscription-pool (TUI) mode".
New env vars: CLAUDE_TUI_MODE, CLAUDE_TUI_WALLCLOCK_MS, OCP_TUI_CWD, OCP_TUI_HOME.
New ADR: docs/adr/0007-tui-interactive-mode.md.
New modules: lib/tui/transcript.mjs, lib/tui/session.mjs (shipped in preceding commits on this branch).
Model — add claude-opus-4-8
Add claude-opus-4-8 as the newest Opus to models.json (index 0, newest first). Repoint aliases.opus from claude-opus-4-7 to claude-opus-4-8. claude-opus-4-7 remains in the list callable by literal id. legacyAliases.claude-opus-4 left pointing at claude-opus-4-7 (no change — legacy alias tracks the prior generation). README Available Models table and model-count references updated accordingly.
v3.16.4
v3.16.4 — 2026-05-13
Refactor — port-literal SPOT + CI guardrail
Closes the structural side of the port-drift cascade addressed by v3.16.2
and v3.16.3. Those two releases reverted plist / plugin / scripts back to
3456 line-by-line, but the underlying invitation to drift — a hardcoded
port literal scattered across six source files — was still intact.
Changes:
- New
lib/constants.mjs— single source of truth for shared literals.
ExportsDEFAULT_PORT = 3456,LOCAL_HOST = "127.0.0.1",
OPENAI_API_BASE = "/v1",LOCAL_PROXY_URL. server.mjs:127,setup.mjs:36,scripts/upgrade.mjs:137,
scripts/doctor.mjs:84+:205,scripts/sync-openclaw.mjs:73—
all replaced with imports fromlib/constants.mjs. Behavior is
identical; the literal3456now exists in exactly one place per
language (lib/constants.mjsfor.mjs,ocp+ocp-connectfor
bash,test-features.mjsfor pinned historical-port tests)..github/workflows/alignment.yml— extended the path filter to
setup.mjs,scripts/**,lib/**,ocp,ocp-connect. Added a new
port-spothard-fail job that greps for any hardcoded3478or3456
literal in.mjs/.js/.ts/.jsonoutside the EXEMPT_REGEX (which lists
lib/constants.mjs,test-features.mjs, the bash CLIs, docs, and the
workflow itself). Any future PR re-introducing a hardcoded port
literal will be blocked at CI before it can cascade.- Doc comments in
server.mjsenv-var summary andsetup.mjsusage
banner reworded so the literal3456no longer appears as
documentation text (CI grep is intentionally aggressive — it does not
parse comments — so doc strings referenceDEFAULT_PORT from lib/constants.mjsinstead).
No behavior change for any user. CLAUDE_PROXY_PORT env var remains
the runtime override; the only difference is the unset-env fallback
now flows through one shared constant.
ALIGNMENT.md hard-requirements: this PR modifies server.mjs (one-line
import + one literal swap, mechanical). No cli.js operation changed;
the citation requirement does not apply. SPOT principle (Rule 2 spirit)
is the entire motivation.
v3.16.3
v3.16.3 — 2026-05-13
Fixes — completes v3.16.2 port-drift revert
v3.16.2 reverted the plugin / openclaw.plugin.json / README / Mac mini
plist back to 3456 (the historical source default since 593d0dc), but
missed three places in scripts/ that still defaulted to 3478. Those
three lines were the residual cascade source: every time ocp doctor or
ocp upgrade ran without CLAUDE_PROXY_PORT in the env, they probed
3478, reported "OCP not responding" against a healthy 3456 instance,
and (in the case of OpenClaw sync follow-ups on the maintainer's host)
re-introduced 3478 into downstream config.
Changes:
scripts/upgrade.mjs:137— default port3478→3456.scripts/doctor.mjs:84— default port3478→3456.scripts/doctor.mjs:205— default port3478→3456.
No behavior change for users who set CLAUDE_PROXY_PORT explicitly; env
still takes precedence. The fix only affects the unset-env fallback,
which now matches server.mjs:126 and the rest of the codebase.
Test plan: existing test-features.mjs cases that pin
CLAUDE_PROXY_PORT=3478 continue to pass — they use the env path, not
the default.
v3.16.2
v3.16.2 — 2026-05-12
Fixes — corrects v3.16.1
The v3.16.1 fix was directionally correct (plugin now reads env first, falls back to a hardcoded default) but the narrative and the hardcoded default were both wrong.
What v3.16.1 said: "OCP server moved to 3478 default in v3.14+; plugin lagged at 3456."
What is actually true:
- OCP server source default has been
3456since593d0dc(initial release) and has never changed. Every line inserver.mjs,setup.mjs, and theocpCLI still uses3456as the documented and code-level default. - The single OCP installation observed on
3478is the maintainer's Mac mini, whose plist was rewritten with--port 3478during a PR #71 dogfood smoke-test accident on 2026-05-08 (see~/.cc-rules/memory/learnings/subagent_setup_mjs_prod_host_collision.md). The plist drift was never reconciled back to source default, and v3.16.1 incorrectly canonised the post-accident value as if it had been a release decision.
This release:
- Restores the plugin fallback to
http://127.0.0.1:3456to match server source default. - Updates
openclaw.plugin.jsonconfigSchema.proxyUrl.defaultback to3456. - Restores README §"Environment Variables"
CLAUDE_PROXY_PORTdefault to3456. - Plugin reads
OCP_PROXY_URLenv (full URL) first, thenCLAUDE_PROXY_PORTenv (port only), then falls back to3456. Hosts whose OCP plist injects a non-default port must also inject the sameCLAUDE_PROXY_PORTinto the OpenClaw plist for the plugin to follow. - Maintainer's Mac mini plist was reverted from
3478to3456as part of this release deploy (no source change reflects this; it was a one-host correction).
Governance
- No
cli.jscitation needed (noserver.mjschange). ALIGNMENT.md Rule 2 not engaged.