Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,60 @@

- **`CACHE_FIX_DOWNLOAD_REWRITE=on` disables `claude update` entirely**, which the flag's name does not suggest. Rewriting a download URL requires MITM-ing `downloads.claude.ai`, whose release client pins public roots only, so the version check fails before anything downloads. It cannot be narrowed to the binary path (MITM is decided per host at `CONNECT`, and the version check shares the host) and no client-side override reaches that client. Documented with the measurement in the README.

## [4.4.0-beta.0] - 2026-08-07

Headline: **the attribution series.** This release is dominated by ten weeks of work from [@Gunther-Schulz](https://github.com/Gunther-Schulz) on one question — *when a cache bust happens, whose fault is it?* — and the answer turned out to require instrumenting the request path before we could fix anything on it. Two of the resulting features change what the proxy sends upstream. All are additive; nothing on the wire changes for an operator who sets no new env vars.

**This is a beta, published on the npm `next` tag, and that is not a formality.** The dogfood host ran v4.3.0 for the entire development window of this release — none of the features below has executed against live traffic anywhere. The promote criteria for `v4.4.0-beta.0` → `v4.4.0` are written down in [`docs/releases/v4.4.0-beta-promote-criteria.md`](docs/releases/v4.4.0-beta-promote-criteria.md), including what would count as a failure, because a soak window with no stated pass condition promotes on the absence of complaints rather than on evidence.

### Added

- **Cache-key attribution: `capture` + `prefix-diff` ([#275](https://github.com/cnighswonger/claude-code-cache-fix/pull/275), [#280](https://github.com/cnighswonger/claude-code-cache-fix/pull/280); contributed by [@Gunther-Schulz](https://github.com/Gunther-Schulz)).** `capture` records what Claude Code *actually sent* at order 60, before any mutating extension runs, so a divergence present in the raw capture is CC's and one absent from it is ours. Without that line there is no way to distinguish a mitigated upstream bug from a self-inflicted one — which was learned the expensive way: **five of six suspected CC bugs turned out to be our own.** `prefix-diff` is the reader for it, reworked from a head-only preview into full attribution: marker-anchored and tail snapshot windows (long-session busts sat past the head window on every deep request and were simply invisible), coverage of every field the cache actually keys on (`model`, `max_tokens`, `thinking`, `output_config`, `speed`, `betas`, and the `anthropic-beta` header — each a measured blind spot that previously let a real bust report "no differences"), and per-tenant baselines keyed by agent-id header falling back to the full system-prompt hash, because truncating to 400 characters merged distinct agents into one baseline. Both are diagnostic surfaces; neither changes what is sent.

- **`insertion-normalization` — volatile reminder blocks no longer bust the prefix ([#272](https://github.com/cnighswonger/claude-code-cache-fix/pull/272); contributed by [@Gunther-Schulz](https://github.com/Gunther-Schulz)).** Claude Code rewrites parts of conversation history mid-session; when a rewritten block sits ahead of the cache boundary the entire prefix re-bills. The extension pins the volatile blocks so the rewrite lands behind the boundary instead of ahead of it. **Carries `needs-sim-validation`** — the fork-context case cannot be reproduced locally, and the beta soak is its validation; see the waiver on #272.

- **`deferred-tool-rewrite` — hold `tools[]` byte-stable ([#273](https://github.com/cnighswonger/claude-code-cache-fix/pull/273); contributed by [@Gunther-Schulz](https://github.com/Gunther-Schulz)).** The name reads backwards and is worth stating plainly: **this extension does not rewrite `tools[]`. It exists so that we never have to.** Every ToolSearch/deferred-tool load makes Claude Code re-send a different `tools[]` array; `tools[]` heads the cache prefix, so each load re-bills the entire context — the class reported in [anthropics/claude-code#81967](https://github.com/anthropics/claude-code/issues/81967), there triggered by LSP add/remove, but hit far more often on the deferred-tool path. Anthropic's API already ships the fix as the documented `mid-conversation-tool-changes-2026-07-01` beta, and Claude Code 2.1.220 carries that beta's documentation in its own binary without using it on the wire. The extension freezes `tools[]` at its first-seen form per session — keyed on session + system prompt + conversation, so subagents and sidecars never share a baseline — and announces mid-session additions through the beta channel instead of mutating the array. **Load-bearing:** it changes what we send upstream.

- **`output-guard` — a last-line invariant on the outgoing request ([#278](https://github.com/cnighswonger/claude-code-cache-fix/pull/278); contributed by [@Gunther-Schulz](https://github.com/Gunther-Schulz)).** Behind `CACHE_FIX_OUTPUT_GUARD=1`, **default off.** A pipeline of body-mutating extensions needs one check that runs last and asks a different question: is the body we are about to send upstream still structurally valid, in every way Anthropic's API and the client's session depend on? The guard validates hard invariants on the outgoing request body — `tool_use`/`tool_result` adjacency, marker budget, role validity and system-message placement, content presence, assistant-terminal shape — and on violation **restores the pre-mutation body Claude Code originally sent**, forwarding that instead of the pipeline's output. A mutation bug becomes one logged `CRITICAL` line and a `guard-events.jsonl` row instead of a corrupted session. It fails open in both directions that matter: a validator crash forwards the mutated body with a `WARN` rather than breaking the request, and a missing stash forwards as-is and says so loudly. **This ships as two extension files and is one feature:** `output-guard-stash` at order 55 clones the body before the first mutator, `output-guard` at order 690 validates and restores from that clone. The pipeline loads one default export per file and the two halves must run at opposite ends of it; the split is scoping, not two features. **It guards against us, not against Claude Code** — the failure it exists to catch is our own extension chain producing a body the client never wrote.

- **`usage-log` emits `ttl_tier` and `duration_ms` ([#320](https://github.com/cnighswonger/claude-code-cache-fix/pull/320)).** Behind `CACHE_FIX_USAGE_LOG_EXTENDED=on`, **default off.** Consumers need [claude-code-meter](https://github.com/cnighswonger/claude-code-meter) v0.9.1 or later, whose row schema is a `strictObject` — an older meter silently drops any row carrying the new keys rather than erroring. Off by default means the blast radius is bounded to operators who explicitly opt in and have upgraded.

- **`tier-advisor` CLI ([#244](https://github.com/cnighswonger/claude-code-cache-fix/pull/244)).** Recommends cache TTL tier upgrades or downgrades from measured session behavior rather than guesswork. Closes [#63](https://github.com/cnighswonger/claude-code-cache-fix/issues/63).

- **`session-budget-breaker` ([#262](https://github.com/cnighswonger/claude-code-cache-fix/pull/262)).** Opt-in per-session hard spend ceiling that short-circuits `/v1/messages` locally once a session crosses a token, dollar, or rate limit — capping the [anthropics/claude-code#68285](https://github.com/anthropics/claude-code/issues/68285) single-session fan-out before it reaches Anthropic. Fails open on every uncertain path.

### Fixed

- **Extension-mutated headers now actually reach upstream ([#274](https://github.com/cnighswonger/claude-code-cache-fix/pull/274); contributed by [@Gunther-Schulz](https://github.com/Gunther-Schulz)).** They never did. Any extension that set a header was writing to an object the forward path did not read.

- **`thinking-block-sanitize` protects continuations by shape, not by tail distance ([#279](https://github.com/cnighswonger/claude-code-cache-fix/pull/279); contributed by [@Gunther-Schulz](https://github.com/Gunther-Schulz)).** Distance from the tail is a proxy for "is this an active tool continuation" that fails whenever the conversation grows between turns.

- **A growing conversation is not an upstream change ([#282](https://github.com/cnighswonger/claude-code-cache-fix/pull/282); contributed by [@Gunther-Schulz](https://github.com/Gunther-Schulz)).** `upstream-change-detection` treated ordinary conversation growth as a signal that Claude Code had changed under us, which is the one thing it must not do — the detector fires an alarm operators are meant to act on.

- **A supervised stop exits 0 ([#277](https://github.com/cnighswonger/claude-code-cache-fix/pull/277); contributed by [@Gunther-Schulz](https://github.com/Gunther-Schulz)).** Lingering streams are forced closed rather than held open until a service manager reports a clean shutdown as a failure.

- **`--remote-control` no longer clobbers another component's `NODE_EXTRA_CA_CERTS`, and the `ca-trust.pem` guard asks node's loader instead of predicting it ([#283](https://github.com/cnighswonger/claude-code-cache-fix/pull/283), [#296](https://github.com/cnighswonger/claude-code-cache-fix/pull/296); contributed by [@codeslake](https://github.com/codeslake)).** Both entries in full under **[Unreleased]** above, which this release promotes. The second is worth reading as a method: a regex modelling node's CA loader took five review rounds and was still wrong in *both* directions on real bundles, and the fix was to stop modelling and start asking — spawn a child with the variable set, stand up a TLS server, connect to it. ~25 ms, once per launch.

- **`--remote-control` no longer routes localhost traffic through the proxy ([#257](https://github.com/cnighswonger/claude-code-cache-fix/pull/257)).** Forward-proxy mode set `HTTPS_PROXY` without `NO_PROXY`, so every client connection — including to HTTP/SSE-transport MCP servers on `127.0.0.1` — went to the cache-fix proxy, which 404s anything that is not `api.anthropic.com`. stdio-transport MCP servers were unaffected, which is why this survived v4.3.0: the common case kept working. `127.0.0.1,localhost,::1` are now merged into any existing `NO_PROXY`/`no_proxy` in forward mode. **A v4.3.0 regression** — anyone who adopted the release's headline feature with a local HTTP MCP server hit it.

- **RFC 7230 absolute-form request-targets are honored in forward-proxy mode ([#261](https://github.com/cnighswonger/claude-code-cache-fix/pull/261); contributed by [@codeslake](https://github.com/codeslake)).**

- **Bounded `strlcpy`/`snprintf` in the VS Code wrapper ([#294](https://github.com/cnighswonger/claude-code-cache-fix/pull/294); contributed by [@anupamme](https://github.com/anupamme)).** First contribution — a security hardening fix in C we had not caught ourselves.

- **Model pricing refreshed; the cost ceiling stops pricing unknown models at zero ([#259](https://github.com/cnighswonger/claude-code-cache-fix/pull/259)).** `tools/rates.json` prices `session-budget-breaker`'s `_COST_USD` lever, which for direct-API-key users is a literal dollar ceiling. `claude-opus-5` was absent from it entirely, so traffic on that model priced at zero and the ceiling never tripped. Adds current pricing for the Opus 5 / 4.8 / 4.7, Fable 5, Sonnet 5, Mythos 5 and bare Haiku 4.5 identifiers, plus a `tools/update-rates.mjs` fetcher that **fails closed on every uncertainty** — a required live-traffic model missing, an ambiguous or not-yet-effective row, a price outside a sane band, cache prices contradicting the documented multipliers — because a plausible-looking wrong price on a spend limit is worse than no update. Fetches to a reviewable PR, never a silent write. Closes [#258](https://github.com/cnighswonger/claude-code-cache-fix/issues/258).

### Documentation

- **Machine-assisted caveat on every translated README, and all three regenerated ([#311](https://github.com/cnighswonger/claude-code-cache-fix/pull/311), [#312](https://github.com/cnighswonger/claude-code-cache-fix/pull/312), [#313](https://github.com/cnighswonger/claude-code-cache-fix/pull/313), [#315](https://github.com/cnighswonger/claude-code-cache-fix/pull/315)).** `README.zh.md` and `README.ko.md` were pinned at 2026-06-15 — thirteen commits and +458/−2 behind English, still advertising v3.0.3 against a current v4.x — and `docs/guia-pt-br.md` was older still, documenting a preload mode superseded since v3.0.0. The caveat header is **permanent policy**, not a flag to remove once a native speaker reviews: a machine-assisted translation stays machine-assisted. Original translators keep their attribution — [@VictorSun92](https://github.com/VictorSun92) (zh), [@ArkNill](https://github.com/ArkNill) (ko), [@thepiper18](https://github.com/thepiper18) (pt-br); regeneration updates their work, it does not replace it.

- **Benchmarking methodology for external evaluators ([#317](https://github.com/cnighswonger/claude-code-cache-fix/pull/317)),** including a Limitations section stating where this proxy is the wrong tool, meant to be read before adoption rather than after.

- **Pre-publication guards directive ([#302](https://github.com/cnighswonger/claude-code-cache-fix/pull/302)).** Directive only; implementation deferred to v4.4.1. Records why the hook is a chain wrapper and explicitly **not** `core.hooksPath` — which would blow away a maintainer host's existing `post-merge`/`post-checkout` hooks — so the next person to reach for it finds the reason instead of rediscovering it.

- **Label semantics split: `approved-by-lead` is a review record, `ready-for-merge` is the merge gate ([#323](https://github.com/cnighswonger/claude-code-cache-fix/pull/323)).** The two used to be one label meaning two things, and stamping the first silently satisfied the second. `ready-for-merge` is now the maintainer's alone; its absence alongside the agent approvals is the normal waiting-on-human state, not an oversight to be corrected.

- **Two capture-derived-data closures ([#307](https://github.com/cnighswonger/claude-code-cache-fix/pull/307), [#319](https://github.com/cnighswonger/claude-code-cache-fix/pull/319)).** A transcript-shape fixture rebuilt from known-safe parts, and two real session UUIDs scrubbed from committed review artifacts.

## [4.3.0] - 2026-07-17

Headline: **Remote Control works through the proxy.** Claude Code ≥ 2.1.196 disables Remote Control / mobile session visibility (and `/schedule`, claude.ai MCP connectors) whenever `ANTHROPIC_BASE_URL` is set — which is exactly how reverse-proxy mode routes the client. This release adds an opt-in **forward-proxy mode** that keeps the client first-party (`ANTHROPIC_BASE_URL` unset, `HTTPS_PROXY` set) so those features keep working while the proxy still sees and transforms `/v1/messages`. All changes are additive and backward-compatible; every new mode is opt-in and defaults are unchanged.
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1483,7 +1483,10 @@ Constraints that assume you've already decided to run this:
- **[@ojura](https://github.com/ojura)** — Opus 4.7 thinking-summaries root-cause analysis: filed [anthropics/claude-code#59844](https://github.com/anthropics/claude-code/issues/59844) with the CLI-binary decode (`!getIsNonInteractiveSession()` gate at offset 230510599 in v2.1.142) and the two-stacked-special-cases framing, which made the `thinking-display` extension (v3.6.1) a clean proxy-side complement to the proposed upstream fix
- **[@yurukusa](https://github.com/yurukusa)** — [Cluster taxonomy](https://yurukusa.github.io/cc-safe-setup/cluster-tracker.html#cluster-extended-thinking-wedge) for [anthropics/claude-code#63147](https://github.com/anthropics/claude-code/issues/63147) thinking-desync wedge; the 13E (ToolSearch) sub-pattern synthesis that made the `thinking-block-sanitize` v2 directive predicate tractable (cache-fix #171, shipped behind `=v2` opt-in in v4.0.0)
- **[@schuay](https://github.com/schuay)** — `quota-statusline.sh` enhancements: 10-cell quota bar with elapsed-time tick and exhaust-vs-reset projection replacing the prior `%/min` burn-rate display (PR #140, v3.6.2), and d/h vs h/m time-format autoselect plus named time-unit and burn-warmup constants (PR #143, v3.7.0)
- **[@codeslake](https://github.com/codeslake)** — Opt-in forward-proxy mode (HTTP `CONNECT` + selective MITM of the upstream host) that keeps Remote Control / mobile session visibility working through the proxy, resolving the `ANTHROPIC_BASE_URL`-disables-RC breakage on CC >= 2.1.196 (PR #251, implements #248); and honoring `CLAUDE_CONFIG_DIR` for all on-disk proxy state so multiple config roots don't clobber each other's credentials/state (PR #246)
- **[@codeslake](https://github.com/codeslake)** — Opt-in forward-proxy mode (HTTP `CONNECT` + selective MITM of the upstream host) that keeps Remote Control / mobile session visibility working through the proxy, resolving the `ANTHROPIC_BASE_URL`-disables-RC breakage on CC >= 2.1.196 (PR #251, implements #248); and honoring `CLAUDE_CONFIG_DIR` for all on-disk proxy state so multiple config roots don't clobber each other's credentials/state (PR #246); RFC 7230 absolute-form request-target handling in forward-proxy mode (PR #261); and the `ca-trust.d` rendezvous that lets the launcher coexist with another MITM on the same host instead of clobbering its `NODE_EXTRA_CA_CERTS` (PR #283), with the follow-up that replaced a regex model of node's CA loader with an actual probe — spawn a child, stand up a TLS server, connect to it — after the regex proved wrong in both directions on real bundles (PR #296)
- **[@Gunther-Schulz](https://github.com/Gunther-Schulz)** — The attribution series: `capture` (PR #275), the pre-pipeline record of what Claude Code actually sent, which is what makes "is this bust ours or upstream's" answerable at all; `prefix-diff` (PR #280) reworked from head-only preview into full cache-key attribution — marker/tail windows, every cache-keyed param and beta header, per-tenant baselines; `insertion-normalization` (PR #272) pinning volatile reminder blocks so mid-history rewrites land behind the cache boundary; `deferred-tool-rewrite` (PR #273) holding `tools[]` byte-stable and announcing mid-session tool additions through Anthropic's `mid-conversation-tool-changes` beta rather than mutating the array; and `output-guard` (PR #278), the last-line response invariant that restores rather than corrupts. Plus the forwarding fix for extension-mutated headers (PR #274), continuation protection by shape rather than tail distance (PR #279), a growing conversation no longer read as an upstream change (PR #282), a supervised stop exiting 0 (PR #277), and the transcript-shape fixture rebuild (PR #307)
- **[@anupamme](https://github.com/anupamme)** — Bounded `strlcpy`/`snprintf` in `claude-vscode-wrapper.c` (PR #294), a memory-safety hardening in the C wrapper we had not caught ourselves
- **[@thepiper18](https://github.com/thepiper18)** — Original Brazilian Portuguese translation (`docs/guia-pt-br.md`, #109 era); regenerated against v4.x reality in PR #315, which updates their work rather than replacing it

If you contributed to the community effort on these issues and aren't listed here, please open an issue or PR — we want to credit everyone properly.

Expand Down
Loading
Loading