chore(release): v3.25.0 — Claude Opus 5, cache-key resolution, counter pairing (#201)

* 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>
This commit is contained in:
dtzp555-max
2026-07-27 08:10:15 +10:00
committed by GitHub
co-authored by taodeng Claude Opus 5
parent f46756b906
commit c998370b8a
4 changed files with 41 additions and 4 deletions
+16
View File
@@ -116,6 +116,22 @@ openclaw gateway restart # so OpenClaw re-reads the config
Future `ocp update` invocations sync automatically.
<a id="cache-rekey-v3250"></a>
### 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 runs `OCP_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 to `CONFIG_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_PROMPT` change 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.
<a id="tui-401"></a>
### TUI-mode returns a permanent `Please run /login` 401 (re-login doesn't stick)