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
+22 -2
View File
@@ -1,12 +1,32 @@
# Changelog
## Unreleased
## v3.25.0 — 2026-07-27
Minor release. Headline: **Claude Opus 5** joins the model list and the `opus` alias now resolves to it. Alongside it, two `server.mjs` correctness fixes found by review rather than by users — a monotonic in-flight-counter leak, and cache keys that were hashing the alias string instead of the model it resolves to. The three TUI/prompt fixes that landed after v3.24.0 are included here too.
Every code PR carried an independent fresh-context reviewer (Iron Rule 10); #192 additionally went through the external codex gate, which is what surfaced the cache-key defect. No new endpoint, no new env var, no new `cli.js` wire behavior.
### Added
- **Claude Opus 5 (`claude-opus-5`), and the `opus` alias now resolves to it.** New `models.json` entry — `/v1/models` goes 6 → 7 and OpenClaw picks it up on the next `ocp update` (via `scripts/sync-openclaw.mjs`). Verified against the installed CLI rather than assumed: the compiled `claude` 2.1.220 bundle carries `latest_per_family:{fable:"claude-fable-5",opus:"claude-opus-5",sonnet:"claude-sonnet-5",haiku:"claude-haiku-4-5"}`, so repointing `opus` mirrors what the CLI itself defaults to — the same reasoning as #168 (sonnet → sonnet-5). Availability confirmed with a live `claude -p --model claude-opus-5` completion on the subscription pool. Pricing is unchanged from Opus 4.8 ($5/$25 per MTok, CLI registry `pricing:"tier_5_25"`), so the alias repoint carries no cost change. `claude-opus-4-8` is retained for pinning.
- **Claude Opus 5 (`claude-opus-5`) (#192).** New `models.json` entry — `/v1/models` goes 6 → 7 and OpenClaw picks it up on the next `ocp update` (via `scripts/sync-openclaw.mjs`). Verified against the installed CLI rather than assumed: the compiled `claude` 2.1.220 bundle carries `latest_per_family:{fable:"claude-fable-5",opus:"claude-opus-5",sonnet:"claude-sonnet-5",haiku:"claude-haiku-4-5"}`. Availability confirmed with a live `claude -p --model claude-opus-5` completion on the subscription pool. `claude-opus-4-8` is retained for pinning.
- `contextWindow` is deliberately **200000**, not Opus 5's native 1M. Two reasons, both verified: (1) `MAX_PROMPT_CHARS` is a **single global** budget — `derivePromptCharBudget` takes `max(contextWindow) × 3` across *all* entries (`lib/prompt.mjs`), so a 1M entry would raise the truncation ceiling to 3,000,000 chars for `claude-haiku-4-5` too, which is genuinely 200k native, converting clean OCP-side truncation into an upstream API rejection; (2) OpenClaw scales its history budget linearly off this value (`contextWindow × maxHistoryShare × SAFETY_MARGIN` = `× 0.6`, plus an oversized-message threshold at `× 0.5`, per `compaction-planning` in OpenClaw 2026.7.1), and its own bundled registry hardcodes 200000 for Claude — the upstream request to raise it to 1M ([openclaw#22979](https://github.com/openclaw/openclaw/issues/22979)) was closed *not planned*. A new regression test pins the invariant so a future 1M entry has to be a deliberate, reviewed change. Raising it for real needs per-model budgets — tracked separately, ADR-level.
### Changed
- **The `opus` alias now resolves to `claude-opus-5` instead of `claude-opus-4-8` (#192).** Every request that names `opus` — and OpenClaw's opus entry — moves to Opus 5 on upgrade. This mirrors what the CLI itself defaults to (`latest_per_family.opus` above), the same reasoning as #168's `sonnet``claude-sonnet-5` repoint in v3.23.0. **Pricing is unchanged** ($5/$25 per MTok; CLI registry `pricing:"tier_5_25"` for both Opus 4.8 and Opus 5), so this carries no cost change. Pin `claude-opus-4-8` explicitly to stay on the previous model.
### Fixed
- **Cache keys hashed the alias, not the model it resolves to (#194).** `model` enters `cacheHash` exactly as the client sent it, so a request for `"opus"` was cached under the literal `"opus"` — and since `models.json` is read once at boot while the SQLite `response_cache` outlives a restart, repointing an alias kept serving the **old model's** answers under it until TTL expiry. That would have silently defeated this release's own `opus` repoint for anyone running with the cache on. All three call sites now hash `Object.hasOwn(MODEL_MAP, model) ? MODEL_MAP[model] : model`, which fixes the normal, structured **and** single-flight keys at once and covers `legacyAliases` for free, with precise invalidation (only the repointed alias's entries change key) rather than a whole-cache flush. (`hasOwn` rather than a bare lookup: `MODEL_MAP` is a plain object, so `MODEL_MAP["__proto__"]` would return a truthy *object* and not even fall through to the `|| model` guard.) **Also closes a latent gap from #177:** the structured and dedup keys never passed `configEpoch` at all, so a `CLAUDE_SYSTEM_PROMPT` change — the original #176 scenario — kept serving structured answers composed under the old config, live since #153. Found by the external codex review of #192.
- **In-flight request counter leaked permanently on a pre-spawn throw (#193, reported by @konceptnet in #180).** `stats.activeRequests` was incremented ~40 lines before the child spawn, while its only decrement is reached through that process's events — so any synchronous throw in between (`buildCliArgs`, env assembly, the spawn decision, or `spawn()` itself) leaked `+1` forever, and `/health` and `/status` over-reported in-flight work monotonically. The increment now sits immediately after `activeProcesses.add(proc)`, structurally pairing it with the decrement; no reconciliation pass and no try/catch. Observability-only field, so no admission decision changes.
- **TUI: the host `CLAUDE.md` could leak into proxied turns (#187, contributed by @sumlin).** The TUI pane now spawns with `--safe-mode`.
- **TUI: `shift+tab to cycle` is accepted as an input-ready marker (#188, contributed by @sumlin).** Claude renders one of two ready-state footers depending on the build — the classic `? for shortcuts` hint, or `shift+tab to cycle` (as part of `⏵⏵ bypass permissions on (shift+tab to cycle)`) on newer 2.1.x. The matcher only knew the classic string, so on those builds it silently reported "never ready": every boot timed out with `tui_pane_not_ready`, and with the warm pool on, every pre-boot failed.
- **`OCP_LOCAL_TOOLS` no longer hard-codes a tool list in its wrapper (#191, closes #185).** The positive wrapper claimed a fixed set of tools regardless of `CLAUDE_ALLOWED_TOOLS`, so a narrowed tool surface was described inaccurately to the model.
### Testing
- The response-cache and counter fixes ship with **mutation-proven integration tests** built on the existing `ltBoot` child-process fixture (real `server.mjs`, fake `claude` binary, so no quota cost). The counter test reaches a synchronous fault *inside* `spawnClaudeProcess` from environment alone — no production fault hook — by running the child under `--stack-size=200` to lower the spread-throw threshold enough to fit Linux's `MAX_ARG_STRLEN`. Suite: **447 → 457** across this release (per PR: #188 +1, #187 +1, #191 +0, #194 +4, #192 +3, #193 +1).
## v3.24.0 — 2026-07-21
Minor release. Headline: two long-requested **OpenAI-compat features** land — **multimodal vision** (`image_url` parts) and **structured outputs** (`response_format` / JSON schema). Also: the prompt-char budget now derives from the model SPOT instead of a hand-set constant, an agentic-turn bug that dropped the model's final answer is fixed, and `OCP_LOCAL_TOOLS` supports the OpenClaw-backend use case. Four of the six landed from external contributors (@vvlasy-openclaw). Every code PR carried a fresh-context reviewer (Iron Rule 10); no new endpoint, no new `cli.js` wire behavior.