* chore(release): v3.25.0 — Claude Opus 5, cache-key resolution, counter pairing
package.json 3.24.0 -> 3.25.0. MINOR because the `opus` alias now resolves to a
different model: every request that names `opus` (and OpenClaw's opus entry)
silently moves from Opus 4.8 to Opus 5. Same $5/$25 per MTok, so no cost change,
but it is a user-visible default change and not a patch.
Consolidates six PRs. Three were already on main without CHANGELOG entries
(#187, #188, #191, merged after the v3.24.0 tag) and are covered here:
#192 Claude Opus 5 + `opus` alias repoint
#194 cache keys hash the resolved model; structured path gets its epoch
#193 active-request counter paired to the process lifecycle
#191 OCP_LOCAL_TOOLS wrapper no longer hard-codes a tool list
#187 TUI --safe-mode so the host CLAUDE.md cannot leak
#188 TUI accepts `shift+tab to cycle` as input-ready
release_kit walk (CLAUDE.md Iron Rule 5.5), every item checked rather than
assumed:
- version_source package.json -> bumped
- changelog CHANGELOG.md -> v3.25.0 section added
- Available Models table -> already 7 rows (landed in #192)
- models.json source_of_truth -> already updated (#192)
- Environment Variables table -> no new env var this cycle
- API Endpoints table -> no new endpoint
- new CLI subcommand / hook / file -> none
- bootstrap_quirk_policy -> see below
Bootstrap quirk documented, per policy. #194 changes the cache key shape, so
alias-addressed rows orphan once and are reaped by the TTL cleanup within one
window — the same shape as the v3.13.0 v1->v2 hash upgrade already recorded in
README. Added to BOTH places the precedent lives: the Response Cache section in
README, and a dedicated entry in docs/troubleshooting.md alongside the existing
"OpenClaw shows old models after ocp update" one-time-quirk entry. Only affects
instances running with CLAUDE_CACHE_TTL > 0; it is off by default.
Verified: release.yml's awk extractor matches the `## v3.25.0 — 2026-07-27`
heading and pulls 22 lines (a mismatched heading would ship an empty release
body). Suite 457 passed, 0 failed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbqUZ8HfBZpjjbzQ85oH8
* fix(release): correct four inaccuracies the reviewer found in the release notes
This PR's entire job is that the text is accurate, so these are not nits.
1. README + troubleshooting overstated the cache-key scope. "Rows keyed on a
literal model id are unaffected" is FALSE for structured-output rows: #194
also added `configEpoch` to the structured key, and keys.mjs:362 folds it
into the digest, so EVERY structured row rekeys regardless of how it was
addressed. Now states both scopes separately — normal cache: only
alias-addressed rows; structured cache: all rows, plus why (the epoch was
never in that key, so a CLAUDE_SYSTEM_PROMPT change did not invalidate
structured answers either).
2. The bootstrap quirk was documented in README § Response Cache and in
docs/troubleshooting.md, but NOT in the "Bootstrap quirks (one-time
migrations)" bullet list at README:540 — which is the location the
release_kit YAML actually names, and where both existing precedents live.
Added there. My "every item checked, not assumed" claim was wrong on the one
item that had a dedicated home.
3. #188's root cause was invented. I wrote "plan/auto mode renders a different
idle footer than manual mode", which contradicts the landed code comment
(lib/tui/session.mjs:184-189) and the test fixture: the two footers are
across claude VERSIONS — the classic `? for shortcuts` vs `shift+tab to
cycle` on newer 2.1.x — and the fixture is bypass-permissions mode, neither
plan nor auto. Replaced with the contributor's own documented cause, plus
the observable symptom (tui_pane_not_ready on every boot, every pre-boot
failing with the warm pool on). Substituting my guess for an external
contributor's evidenced diagnosis would have sent the next debugger looking
for "plan mode".
4. "Suite: 449 → 457" used a mid-cycle baseline. Measured at the v3.24.0 tag:
447. 449 is the count AFTER #187/#188 already landed — and this release
claims those as part of the cycle. Corrected to 447 → 457 with the per-PR
breakdown (#188 +1, #187 +1, #191 +0, #194 +4, #192 +3, #193 +1 = +10).
Also taken from the same review:
- the Added bullet now cites #192 (every Fixed entry had a PR number; Added
did not)
- split out a `### Changed` section for the `opus` repoint, matching how
v3.23.0 recorded #168's `sonnet` repoint — a behavior change should not sit
under Added
- CHANGELOG quoted `MODEL_MAP[model] || model`, the form review REJECTED;
the landed code is the Object.hasOwn guard (a bare lookup returns a truthy
object for "__proto__" and never reaches the `||` fallback)
Re-verified after the edits: release.yml's awk extractor yields 27 lines /
6277 bytes with all four sections; all six PR references present. Suite 457
passed, 0 failed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbqUZ8HfBZpjjbzQ85oH8
* fix(release): qualify the normal-cache scope for OCP_LOCAL_TOOLS instances
Round-2 review. One finding accepted, one REJECTED with first-hand evidence.
ACCEPTED [MED] — my round-1 fix corrected the structured-cache scope but got
the normal-cache scope wrong in a new way. "Rows keyed on a literal model id
keep matching" is true for #194 in isolation, false for this RELEASE: #191
(a32bc9a, in this same release) reworded OCP_LOCAL_TOOLS_WRAPPER, the wrapper
text is one of CONFIG_EPOCH's four inputs (server.mjs:219), and every normal
cache key folds the epoch in. Verified by computing both digests:
epoch with the OLD local-tools wrapper: 9a97a9dace3cadc2
epoch with the NEW local-tools wrapper: 9f4526f7b146adfb
So an OCP_LOCAL_TOOLS=1 instance rekeys its ENTIRE normal cache once, literal
ids included. The doc heading is release-scoped while my claim was PR-scoped —
the same class of over-broad assertion as the original finding, which I then
reintroduced while fixing it. Both docs now carry the exception.
Also anchored the README bootstrap-quirk link at the specific troubleshooting
section (#cache-rekey-v3250) rather than the file, matching the existing
#tui-401 precedent.
REJECTED [claimed HIGH] — "Opus 5 is pricing:\"tier_10_50\" ($10/$50), so
'Pricing is unchanged' is false." It is not false. I re-extracted from the same
binary the review cites (claude 2.1.220), this time ANCHORED ON THE id FIELD
rather than on a bare id string:
id:"claude-opus-5" ... pricing:"tier_5_25"
id:"claude-opus-4-8" ... pricing:"tier_5_25"
id:"claude-mythos-5" ... pricing:"tier_10_50" <- the real owner
id:"claude-sonnet-5" ... pricing:"tier_3_15"
`claude-opus-5` never appears within 200 chars before a tier_10_50 (grep -c: 0).
tier_10_50 belongs to claude-mythos-5. The review appears to have matched an
adjacent entry — the exact failure mode it accused this PR of, which is a fair
thing to have looked for; it just wasn't what happened here.
Worth recording WHY the original extraction was sound even though it looked
suspicious: the substring it matched, `claude-opus-5"},eager_input_streaming`,
is the tail of claude-opus-5's OWN provider_ids object (`gateway:"claude-opus-5"}`),
so the pricing that follows is its own. Ambiguous-looking, correct in fact —
which is why the id-anchored re-extraction was worth doing rather than assuming
either way.
The pricing claim therefore stands unchanged, on first-hand evidence.
Suite: 457 passed, 0 failed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbqUZ8HfBZpjjbzQ85oH8
---------
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
8.5 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.
Response-cache hit rate drops once after upgrading to v3.25.0
Only affects instances running with the response cache on (CLAUDE_CACHE_TTL > 0); it is off by default, so most installs see nothing.
v3.25.0 keys the cache on the resolved model rather than on the string the client sent, so rows written for an alias (opus, sonnet, haiku, or a legacy alias like claude-haiku-4-5) no longer match. Those rows orphan and are reaped by the TTL cleanup interval within one window — no migration script, no action required; expect one window of extra misses and then normal hit rates.
Two different scopes, worth being precise about:
- Normal cache — only alias-addressed rows rekey. Rows written under a literal model id (
claude-sonnet-5) keep matching — unless the instance runsOCP_LOCAL_TOOLS=1, in which case the entire normal cache rekeys once. That is a separate mechanism: v3.25.0 also reworded the local-tools wrapper, and the wrapper text is one of the four inputs toCONFIG_EPOCH, which every normal cache key folds in (established behavior since v3.23.0, not new here). - Structured-output cache — every row rekeys, alias or literal. The same change also folds the config epoch into the structured key, which it had never included; that gap meant a
CLAUDE_SYSTEM_PROMPTchange did not invalidate structured answers either. Structured caching only exists from v3.24.0, so there is at most one release worth of rows to orphan.
This is deliberate, and it is what makes an alias repoint take effect. Before v3.25.0, changing where an alias pointed (v3.25.0 itself repoints opus → claude-opus-5) left the cache serving the old model's answers under that alias until TTL expiry, because models.json is read once at boot while the SQLite cache survives the restart. If you were running with the cache on and repointed an alias in an earlier version, that is why it appeared not to take.
A side effect worth knowing: an alias and its canonical id now share a cache slot, since both produce an identical spawn. That is a small hit-rate improvement in steady state.
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.