--remote-controlno longer clobbers another component'sNODE_EXTRA_CA_CERTS. That variable takes exactly one file, so on a host where something else also MITMsapi.anthropic.com(a corporate agent, an account-pinning proxy) the last writer won and every other CA was silently untrusted — measured breaking Remote Control inbound. The launcher now publishes its own CA to${CLAUDE_CONFIG_DIR:-~/.claude}/ca-trust.d/ccf.pem(own filename only, never a sibling's, rewritten every launch, atomically via temp +rename) and reads a mergedca-trust.pemif one exists. It never writes the merged bundle: merging needs ambient corporate-root discovery, which is environment-specific and belongs outside this repo. The bundle is used only when every PEM block in it parses and one of them is our own CA (compared by DER) — a bundle that is torn or predates our publish is worse than none, since it makes the client distrust the very proxy it is routed through. On a host with no other MITM and no bundle, behavior is byte-identical to before. Both paths are fixed names under the config dir with no env override: they are two halves of one rendezvous, so a knob on either half alone would let a participant drop out of the contract while appearing to implement it. See Coexisting with another MITM.
CACHE_FIX_DOWNLOAD_REWRITE=ondisablesclaude updateentirely, which the flag's name does not suggest. Rewriting a download URL requires MITM-ingdownloads.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 atCONNECT, and the version check shares the host) and no client-side override reaches that client. Documented with the measurement in the README.
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.
-
Opt-in forward-proxy mode (#251, implements #248; contributed by @codeslake). With
CACHE_FIX_FORWARD_PROXY=on, the proxy also handles HTTPCONNECTand MITMs only the upstream host (api.anthropic.com) with a locally-generated CA, terminating TLS so the same extension pipeline runs, and blind-tunnels every other CONNECT (mcp-proxy, telemetry, npm, …) untouched. The client wiresHTTPS_PROXYat the proxy plusNODE_EXTRA_CA_CERTSat the generated CA, and leavesANTHROPIC_BASE_URLunset — so Claude Code stays first-party and Remote Control / mobile session visibility keeps working. Non-/v1/messagespaths (RC credential fetches, OAuth,/api/*) are relayed verbatim; WebSocket/Upgrade to the upstream host is relayed as-is. The CA is generated once under${CLAUDE_CONFIG_DIR:-~/.claude}/cache-fix-ca/(overrideCACHE_FIX_CA_DIR) with private keys at mode0600; generation is lock-serialized (pid-owned.gen.lock, reclaimed only if the owner is dead) and the leaf's key/cert pair is proven to match (SPKI compare) before it is served, so concurrent proxies sharing a CA dir can't publish a leaf that doesn't chain to the CA the client was told to trust. A failed attach (e.g.opensslmissing) falls back to reverse-proxy behavior and/healthreports the effective mode (forward_proxy). Default OFF; reverse-proxy behavior is unchanged when the flag is unset. New env vars, all opt-in:CACHE_FIX_FORWARD_PROXY,CACHE_FIX_CA_DIR,CACHE_FIX_CA_FORCE_ROTATE,CACHE_FIX_CA_LOCK_WAIT_MS, plus opt-in download-acceleration (CACHE_FIX_DOWNLOAD_REWRITE/_BUCKET/_BINARY). Load-bearing (TLS termination, MITM CA, credential-bearing wire path): reviewed by Codex and human lead, and validated end-to-end against a live session (Remote Control restored, phone connected). See the Forward-proxy mode section of the README, which also documents the shared-service crash-semantics tradeoff. -
--remote-controllauncher flag (#254).cache-fix-proxy --remote-controlis the one-command equivalent of the manual forward-proxy wiring: it spawns the proxy withCACHE_FIX_FORWARD_PROXY=on, waits for the CA, and launchesclaudewithHTTPS_PROXY+NODE_EXTRA_CA_CERTSset andANTHROPIC_BASE_URLleft unset. Without the flag the launcher stays in reverse-proxy mode (setsANTHROPIC_BASE_URL), unchanged. The launcher resolves the CA path from the same inputs and precedence as the proxy (CACHE_FIX_CA_DIRfirst, thenCLAUDE_CONFIG_DIR, then~/.claude).cache-fix-proxy --helpdocuments two operator caveats found in live validation: Remote Control's trusted-device enrollment can need a few/remote-controlretries on first connect (a Claude Code step that runs upstream, not a proxy failure), and enabling RC on an already-warm session costs a single one-time prompt-cache rebuild (RC adds ananthropic-betathe cache keys on) — launching with--remote-controlfrom the start avoids that flip.
-
Honor
CLAUDE_CONFIG_DIRfor all on-disk proxy state (#246; contributed by @codeslake). All on-disk proxy state (quota-status, usage.jsonl, session-mirrors, snapshots, OAuth/credential paths, the forward-proxy CA dir) now resolves underCLAUDE_CONFIG_DIRwhen set, via a singleclaudeHome()helper (CLAUDE_CONFIG_DIR || ~/.claude), matching how Claude Code itself relocates its config root. Previously these were hardcoded to~/.claude, so running one proxy per config dir made them clobber each other'saccount.json/ credentials. Falls back to~/.claudewhen unset — no behavior change for the default single-config setup. -
Statusline
ttl_tierderived from the measured ephemeral split (#252, fixes #247; contributed by @codeslake). The statusline's cache-TTL indicator was guessed fromcache_readpresence (and hardcodedephemeral_5m = 0), which mislabeled two real cases: a fresh 1h-cached prefix (cache_read=0) shown as5m, and a genuine 5m downgrade (cache_read>0with a 5m split) hidden as1h. It now reads the measuredusage.cache_creation.{ephemeral_1h,ephemeral_5m}_input_tokenssplit when the API provides it, falling back to the old heuristic only when the split is absent. A mixed split is labeled by its dominant tier (so a 5m-dominant mixed response now correctly surfaces the redTTL:5mwarning). No output-schema change; the statusline consumes the samettl_tierfield.
- Forward-proxy passthrough success-path is now traceable (#253).
handlePassthroughlogged only on error, so a successful RC-credential relay left no server-side trace. It now emits aCACHE_FIX_DEBUG=1-gated line (method + path + status only — no headers or body, since this path carries credentials), making forward-mode passthrough observable without a client in the loop. No-op unless debug logging is enabled.
- OAuth refresh narrowed token scopes, causing remote-bridge / code-session 401s (hotfix; affects v4.2.0 only when
CACHE_FIX_OAUTH_REFRESH=on). v4.2.0's proxy-owned OAuth refresher hardcoded the refresh-grantscopefield touser:inference user:profile. Real Claude Code credentials hold a wider set (typicallyuser:file_upload user:inference user:mcp_servers user:profile user:sessions:claude_code). When the proxy refreshed, the server happily issued the narrower token —/v1/messageskept working because it only needsuser:inference, but remote-bridge / code-session API calls 401'd becauseuser:sessions:claude_codewas missing from the rotated token. Observed end-to-end on 2026-06-23: proxy refreshed at 17:48 UTC, remote-bridge sessions started returning 401 at 20:01 UTC, recovery required an interactive/login. The fix readsclaudeAiOauth.scopesfrom the credential file inside the in-lock re-read and serializes them verbatim onto the refresh POST (space-separated per RFC 6749 §3.3), with dedupe + filter for malformed entries.CACHE_FIX_OAUTH_SCOPEenv var is honored as an operator override; auser:inference user:profilefallback applies only when the credential file has noscopesarray (unusual). 6 new regression tests assert scope round-trip, the load-bearinguser:sessions:claude_codepreservation, persisted-credential scope passthrough, env override, fallback, and dedupe behavior. The fix is purely additive to the refresh-POST body;/v1/messagesupstream traffic was never affected. Operators on v4.2.0 withCACHE_FIX_OAUTH_REFRESH=onshould upgrade immediately — the next proxy-initiated refresh will issue a token that does not work for remote bridges, even though local CC traffic continues.
CACHE_FIX_USAGE_LOG_REQIDis now default-on; env-var becomes a kill-switch (#210 follow-up). Per the v4.1.0 changelog's release-ordering contract, the flip waits until claude-meter ≥ v0.7.0 is published. With meter v0.7.0, v0.7.1, and v0.8.0 all on npm-latest, the precondition is met. v4.2.0 emitsrequest_idon every~/.claude/usage.jsonlrow by default (when the upstreamrequest-idresponse header is present and ≤ 64 chars).CACHE_FIX_USAGE_LOG_REQID=offnow omits the field — the env-var is repurposed as a kill-switch for operators stuck on a pre-v0.7.0 meter install. Schema stays atv: 1(pure semantic flip; field shape unchanged). Upgrade requirement: operators running cache-fix v4.2.0 + claude-meter < v0.7.0 will see every meter row rejected by the strict-object validator until they either upgrade meter or setCACHE_FIX_USAGE_LOG_REQID=off. Seedocs/directives/proxy-usage-log-request-id.mdfor the original release-ordering contract.
-
Proxy-owned OAuth refresh (#234, directive #236, implementation #237). Default-OFF subsystem that makes the cache-fix proxy the single, proactive, lock-cooperative refresher of the OAuth credential shared by all concurrent Claude Code clients running as the same OS user. Closes the refresh-token rotation race that revokes the whole token family and 401s the entire fleet at once — a failure no client-side restart can recover (only an interactive
/login). Acquires the same~/.claude/.oauth_refresh.lockthe client uses viaproper-lockfilewithrealpath:falseandstale:10000to guarantee mutual exclusion against waking clients. New §2a contract: the refresh POST has a hardCACHE_FIX_OAUTH_POST_TIMEOUT_MSdeadline (default 8000 ms, strictly below the client's 10000 ms stale-break window) covering BOTH the headers AND the response body read; on timeout the outcome is UNKNOWN → no credential write, no retry, distinctoauth_refresh_timeoutevent, back off ≥ one stale window. Atomic persist: temp-write (mode 0600) + fsync FD + rename + fsync parent dir, preserving every other credential field. Validation gates on every read: not a symlink, mode 0600, owner-matches-uid, JSON-shape valid; per-failure distinct event. Seven distinct event classes in~/.claude/cache-fix-oauth-events.jsonl:oauth_refreshed,oauth_family_revoked(distinct loud event + stderr banner for the unrecoverable case),oauth_refresh_timeout,oauth_refresh_error,oauth_refresh_skipped,oauth_lock_contended,oauth_cred_*validation events. Threat-model discipline: token material is constructed-into-non-existence on the event path — events carry only{event, outcome, status_code, expires_at, err_class, elapsed_ms}, never the token strings, never the raw POST body, never the raw token-endpoint response body. Addsproper-lockfileas a runtime dependency. Default OFF viaCACHE_FIX_OAUTH_REFRESH=on; inert until an operator deliberately enables it and restarts the proxy. Backout is gate-off + restart. Seedocs/directives/proxy-owned-oauth-refresh.mdfor the full contract + the binary-forensics-corrected root cause (CC 2.1.148 has a lock; the lock has a 10 s stale-break hole; proxy refresh sits inside the hole to win the race by timing AND by holding the same lock). -
cc-version-normalizeextension (#238, #239). Default-off opt-in that rewrites thecc_versionvalue inside thex-anthropic-billing-headerof the system prompt to prevent per-build cache invalidation. Some Claude Code distribution channels (notably the VS Code extension under auto-update) emitcc_versionwith a trailing per-build hash segment on top of MAJOR.MINOR.PATCH, e.g.2.1.185.<buildhash>. That value lives inside the cacheable prefix, so when the build-hash mutates mid-session (binary auto-updates between turns), every subsequent turn pays fullcache_creationcost until the suffix stabilizes — Anthropic's prefix cache is byte-exact and the field is in scope. Existingfingerprint-stripdoes NOT cover this case: it only rewrites suffixes whose value matches a CC-generated fingerprint of the user message text, so a binary build-hash fails verification and the extension returns null without rewriting. The new extension runs at order 90 (beforefingerprint-stripat 100), produces a 3-segment version, andfingerprint-strip'sdotParts.length < 4guard then makes it a no-op — the two cooperate cleanly. Three modes viaCACHE_FIX_NORMALIZE_CC_VERSION:off(default),strip(collapsescc_version=X.Y.Z(.suffix)+tocc_version=X.Y.Z),pin:<value>(replaces with operator literal; useful for fleets that want one stable identifier across all clients). Field-boundary anchored regex(^|[;\s:])cc_version=([^;\s]+)so acc_version=substring embedded in another field's value cannot be accidentally rewritten. Pin-value validation^[A-Za-z0-9.\-]+$(max 64 chars) so semicolons/equals/whitespace fail-open to off with a one-shot stderr warning rather than mutating the body. Atomic fail-open: planned rewrites stage in a local array and apply only after the scan completes; any error during the scan leaves the body byte-intact. Surfaced by @X-15 running cache-fix v4.1.0 — the existingprefix-diffextension correctly flagged the mutation but the proxy had no mitigation path until this. The design contract is in the issue body of #238; no separate directive document was authored. -
upstream-error-logextension (#235, #240). Default-off opt-in that emits a structured JSONL record for every non-200 upstream response to~/.claude/usage-log/upstream-errors.jsonl. The existingusage-logonly records successful (200) responses; non-200 responses (429 capacity throttling, 5xx errors) leave only an unstructured line in the debug log, so server-side throttling has been effectively invisible to any analysis built on the usage stream. Two distinct 429 classes that look identical to a user: account/usage-limit carriesanthropic-ratelimit-unified-*headers +retry-after; infrastructure/capacity is Cloudflare-fronted, carriesx-should-retry: trueonly, NO ratelimit headers (the "Server is temporarily limiting requests, not your usage limit" case). The load-bearing discriminator ishas_ratelimit_headers(bool): with headers → usage limit; without → capacity event. SUPERSET of the existingrate-limit-logextension —rate-limit-logtriggers only on the canonicalrate_limit_errorbody envelope and misses capacity-class 429s whose body shape differs;upstream-error-logtriggers on everystatus >= 400regardless of body shape. Independent JSONL streams (upstream-errors.jsonlvsrate-limit-events.jsonl); analysts join onsession_id + ts. Hook isonResponseStart, notonResponse—onResponseonly fires when the proxy successfully JSON-parses the response body, and many non-200 responses (especially Cloudflare-level errors) return HTML or empty bodies that fail to parse. Record fields:schema_version,ts,type,session_id,requested_model,request_path,response_status,upstream_message,has_ratelimit_headers,ratelimit_status,ratelimit_overage_status,x_should_retry(normalized to bool from string),retry_after,upstream_request_id,upstream_connection_id. Default OFF viaCACHE_FIX_UPSTREAM_ERROR_LOG=on. Override the log path viaCACHE_FIX_UPSTREAM_ERROR_LOG_PATH. -
workflow-agent-id-synthesisextension (#215, refs upstream anthropics/claude-code#66761). Closes the per-Workflow-leg cost-attribution gap that CC#66761 left open: CC setsx-claude-code-agent-idon Task/Agent-tool subagents but not on Workflow-tool–spawned subagents (agent()/parallel()/pipeline()), so fan-out workflows are indistinguishable from the parent conversation's traffic at the proxy. The new extension stashes a normalizedctx.meta._workflowAgentId = { id, parentId, source }ononRequestcovering three states: canonical present (Task subagent — pass-through withsource: "cc_header"), derived (Workflow subagent —sha256(sessionId + markerId + sha256(first-user-message text))[:16]withsource: "cache_fix_derived"), and neither (top-level traffic — no stash). Per-leg discriminator binary-inspection finding: CC's workflow factory keeps every per-leg distinguishing field (CC's internalagentIdUUID,agentType,spawnedByWorkflowRunId, workflow phase index) in IN-PROCESS state, not in the wire request body — the Anthropic Messages API has no slots for them. From the proxy's wire vantage, the only Workflow-distinguishing content the wire carries is the system-prompt marker (used for detection) and the user-suppliedagent(prompt)argument. The first-user-message text digest is the only stable, leg-distinct, retry-deterministic discriminator available. Known limitation: aparallel()fan-out where every leg passes the SAME prompt collides on the discriminator — operators get one bucketed id rather than wrong attribution. Identical-prompt fan-out is uncommon (the canonical pattern is "do thing X to file Y", "do thing X to file Z", ...); when it happens, thecache_fix_derivedsource flag tells dashboards the attribution is heuristic. The derived ids are proxy-only attribution keys; nothing this extension produces reaches Anthropic upstream. Detection requires the conjunction of session-id present, canonical agent-id absent, AND a position-anchored match against the binary-verified marker catalog atproxy/workflow-markers.mjs(seeded from CC 2.1.177 sha256ff41753634b20c869ef6a32a20863521b33d4186ac0d6a49379ab48a48395ee7). Drift canary: when the first two conditions hold but no marker matches, a throttledagent_id_source: "drift_canary"event is written to~/.claude/workflow-derivation-events.jsonl(5 MB single-tier rotation) — the early warning that a new CC release has shifted the marker text. Tools-list churn invariance: the derived id never includestools[*], so adeferred-tools-restoreMCP reconnect race cannot split one leg's traffic across two ids.usage-log.mjsreadsctx.meta._workflowAgentIdand emitsagent_id+agent_id_sourceon its row whenCACHE_FIX_USAGE_LOG_AGENT_ID=on. Cross-repo contract:claude-code-meter >= 0.8.0accepts these fields; older meter installs reject rows that carry them — the env-var is the operator's attestation of meter v0.8.0+, NOT a runtime version probe. Setting it against meter v0.7.x produces rows the strict-object schema rejects (visible symptom: nonzeroskipped=counter onclaude-meter ingesttick output; logged underCLAUDE_METER_DEBUG=1). Master switch:CACHE_FIX_WORKFLOW_AGENT_DERIVATION=offdisables the extension entirely. Default-offCACHE_FIX_USAGE_LOG_AGENT_IDin v4.2.0 first ship per the establishedrequest_idrollout precedent (cache-fix v4.1.0 → v4.2.0); the v4.3.0 default-on flip is gated on a sim-validation precondition against a v0.8.0 meter install on real Workflow fan-out traffic. The directive frames this as derived-not-authoritative: when CC eventually backfills the canonical header upstream, thecanonical_presentbranch fires first and the derivation path goes inert without migration. Seedocs/directives/proxy-workflow-agent-id-synthesis.mdand companion meter PR cnighswonger/claude-code-meter#31. -
Statusline served-model divergence indicator (#223, refs upstream anthropics/claude-code#66728). First real-time operator surface for the classifier-driven swap pattern documented in CC#66728.
proxy/extensions/cache-telemetry.mjsnow capturesevent.message.modelonmessage_startand compares it againstctx.telemetry.requestedModelonmessage_delta, persistingrequested_model/served_model/model_divergence_recent/model_divergence_sticky/model_divergence_first_seento the per-session JSON when the pair diverges.tools/quota-statusline.shrenders the indicator after the existing TTL block: redrequested → servedfor a recent divergence, black-on-yellow for sticky state, no extra label segment on matched turns. Sticky heuristic is family-aware: cross-family swap (Fable→Opus, Sonnet→Haiku) latches sticky immediately; same-family swap (Opus 4.7→Opus 4.8) requires 3 consecutive divergent turns at the same(requestedModel, servedModel)pair. Counter state is keyed by(sessionFilename, requestedModel)so interleaved background utility calls (haiku title-generation, etc.) at a different requested model do not pollute the main-model counter. Sticky survives proxy restart for the currently-active pair via rehydration from the persisted JSON, guarded onrequested_modelequality so the persisted single-record cannot leak sticky into a different pair after a/modelchange. The[1m]suffix renders on the requested side only (the proxy has no signal for served-side 1m context). The family map is the only piece of business logic that needs to update when Anthropic ships new models. Operator note: clearing sticky requires removing the persisted per-session JSON AND evicting the in-memory map entry (restart, time-based sweep, or new session). File deletion alone does not work — the map will re-emit the persisted fields on the next turn. Seedocs/directives/proxy-statusline-served-model-divergence.mdfor the full design. -
image-retry-circuit-breakerextension (#217, refs upstream anthropics/claude-code#66815). Short-circuits the CC harness retry storm on permanentimage could not be processedfailures. When the same session retries with the same image content (SHA-256 of decoded bytes) within a 30s sliding cool-off window, the proxy returns a wire-format-correct synthesized response — SSE event sequence forstream:truerequests, JSON envelope otherwise — so the harness consumes the failure as a normal completed turn instead of resubmitting full context 18 more times. Bounds the loss from CC#66815's reported pattern (19 retries × 34 MB of context tokens) to one upstream call. Default-off in v4.2.0 first ship viaCACHE_FIX_IMAGE_RETRY_BREAKERenv var (on/off/dry-run); tunables areCACHE_FIX_IMAGE_RETRY_COOLOFF_MS(default 30000) andCACHE_FIX_IMAGE_RETRY_MAX_ENTRIES(default 4096). Breaker fires write a structured JSONL event log at~/.claude/image-retry-events.jsonl(5 MB single-tier rotation); short-circuited requests bypassusage-log/cache-telemetryentirely (no row written — the JSONL event log is the sole observability surface). Carriesneeds-sim-validationas a merge gate per the wire-format dependency on real CC harness consumption. Seedocs/directives/proxy-image-retry-circuit-breaker.mdfor the full design. -
tools/gh-auth-status-shim/— contributed PATH-resolvedghwrapper (refs upstream anthropics/claude-code#67055). Workaround — not a fix — for CC Desktop's false "GitHub CLI authentication expired" toast. CC Desktop's PR poller runsgh auth status --hostname github.comwith a 5-secondtimeoutMsand maps any non-zero return (including the spawn timeout itself) to the"auth"toast category, producing repeated false toasts during Keychain slow-reads, network blips, and multi-agent CPU contention. The shim is a bash script namedghplaced earlier on the user's PATH than the realghbinary; it interceptsgh auth statusinvocations, runs the realghwith a ~4s internal timeout (inside CC's 5s window), classifies the outcome, and returns exit 0 to suppress the false toast on transient/timeout signals while letting genuine expiry (not logged in,HTTP 401) propagate so CC's toast fires when it should. Every otherghsubcommand passes through to the real binary unchanged viaexec. Install viatools/gh-auth-status-shim/install.sh(default target$HOME/.local/bin/gh); uninstall via the matching script. bash 3.2 floor (runs on stock macOS); no GNU coreutils dependency; nojq. Known limitations called out at install time and in the README: (1) the shim rewritesgh auth statusexit-code semantics for every caller in this PATH scope, including non-CC tools — documented behavioral-change disclosure, (2) on macOS, GUI apps inheritlaunchd's PATH not the user's shell PATH, so the shim may be invisible to CC Desktop on macOS even ifwhich ghshows it in the shell — macOS coverage is unverified pending external validator from the CC#67055 thread, (3) native Windows CC Desktop is NOT covered by a bash shim. Sunset plan: uninstall when CC#67055 closes with an upstream fix; see the "Sunset plan" section oftools/gh-auth-status-shim/README.md. Seedocs/directives/tool-gh-auth-status-shim.mdfor the full design and the prototype-validation requirement. -
jsonl-session-mirrorextension (refs upstream anthropics/claude-code#66734 and anthropics/claude-code#66486). Belt-and-suspenders backup against CC's in-place transcript stub-rewrite (CC#66734) and missing-transcript regression (CC#66486). The proxy mirrors every assistant message + the tool results / user inputs it observes into a per-session JSONL file under user control, independent of CC's own transcript writer. CC's transcript remains canonical when it survives; the mirror is the user's recovery path when CC's transcript is lost. Storage root~/.claude/session-mirrors/<sessionFilename(sessionId)>/<timestamp>.jsonl. Envelope shape matches CC 2.1.148's verified transcript shape exactly so existing transcript readers (includingrestore-claude-history-linux) parse mirror files unchanged. The single distinguishing field issource: "cache-fix-proxy-mirror". Read-only with respect to upstream traffic; no requests or responses modified. Default-off in v4.2.0 and v4.3.0 viaCACHE_FIX_SESSION_MIRROR=on(privacy-posture change of flipping plaintext-conversation-persistence on-by-default belongs to its own future directive). Tunables:CACHE_FIX_SESSION_MIRROR_DIR,CACHE_FIX_SESSION_MIRROR_MAX_BYTES(default 100 MB),CACHE_FIX_SESSION_MIRROR_RETENTION_DAYS(default 30),CACHE_FIX_SESSION_MIRROR_MAX_SESSIONS(default 1024),CACHE_FIX_SESSION_MIRROR_INCLUDE_THINKING(defaulttrue). Operational events (open / rotate / sweep / error) logged to~/.claude/session-mirrors/session-mirror-events.jsonl(5 MB single-tier rotation). Three known limitations documented at write time: (1)cwdisnull(proxy does not know caller working directory), (2)uuidis dash-formatted (8-4-4-4-12) but the version/variant bits are not RFC-valid — the bits encode(sessionId, timestamp, messageId)deterministically so the chain is reconstructable, (3) tool-result user records omittoolUseResultandsourceToolAssistantUUID(CC-internal enriched objects the proxy cannot reconstruct). Carriesneeds-sim-validationas a merge gate per the envelope-shape parity dependency. Seedocs/directives/proxy-jsonl-session-mirror.mdfor the full design anddocs/disk-usage.mdfor the disk-footprint accounting.
- Optional
request_idfield on usage-log rows (#210). Sources from the upstreamrequest-idresponse header. Gated default-off viaCACHE_FIX_USAGE_LOG_REQID=onfor this release. When enabled, every~/.claude/usage.jsonlrow gains the field, recovering per-CC-session attribution that the proxy-boot-stickysidfield alone cannot provide. The field is the natural post-hoc join key against CC's per-session JSONL transcripts at~/.claude/projects/<project>/<session-uuid>.jsonl(which already carryrequestIdfor every API call). Cross-repo contract:claude-code-meter >= v0.7.0accepts the optional field; older meter installs reject unknown keys via the strict-object schema, hence the gate stays default-off in this release. The gate flips default-on in v4.2.0; operators upgrading to v4.2.0 must run claude-meter v0.7.0+. Schema stays atv: 1(pure addition; no consumer's reading of existing fields changes). Seedocs/directives/proxy-usage-log-request-id.mdfor the full design and the release-ordering contract. - server.mjs debug logging (#190). Opt-in per-request trace log via
CACHE_FIX_DEBUG=1, written to~/.claude/cache-fix-debug.log(override path viaCACHE_FIX_DEBUG_LOG). Captures route-level Claude → Proxy → Upstream traffic for operators debugging proxy behavior. Authorization / x-api-key / cookie / proxy-authorization headers are redacted at capture time (the log writer never touches raw header values). Dispatcher catches async handler rejections inside an awaited try/catch so promise rejections frompreForward()or pipeline hooks no longer escape tounhandledRejection. The 500 fallback body is generic — no internalerror.messageecho. Contributed by @nisqatsi. tools/cache_analysis.pyreference helper (#138). Python helper for reading the proxy's per-session quota-status files at~/.claude/quota-status/sessions/<id>.json(with v3.4.x fallback to~/.claude/quota-status.json). Now version-controlled in this repo and shipped via the existingtools/package.json entry. Closes part 1 ofcnighswonger/claude-code-meter#22— the host-installed copy at~/.claude/mcp/cache_analysis.pyhad been silently returningNonefor 15 days post-v3.5.0 because the local helper lacked the new-path fallback.- install-service threads
CACHE_FIX_PROXY_CA_FILEandCACHE_FIX_PROXY_REJECT_UNAUTHORIZEDto the rendered unit (#189). Corp-proxy and custom-CA configurations now survive install-service round-trips on both systemd and launchd. Hardens the systemdEnvironment=value escape against%(specifier expansion) and\(C-string unescape), and the launchd plist value escape against all five XML entities. Contributed by @nisqatsi.
- Preserve base-path component in upstream URL forming (#188). Configurations that chain cache-fix-proxy through another reverse proxy that mounts the Anthropic API under a path prefix (e.g.,
https://corp.example/api/anthropic/v1/messages) now forward correctly. Previously, the proxy concatenated only the upstream host with the inbound path, dropping the base-path component. Adds a purebuildUpstreamUrl(base, clientUrl)helper with 8 regression cases (no-path, trailing-slash, mirror, multi-segment, query strings, http+port). Contributed by @nisqatsi.
- Scrub npm token location, org name, and rotation cadence from public docs (#208).
docs/release-workflow.mdstep 8 previously named the on-disk token path, thevsitsllcnpm org, and the 90-day rotation cadence — the combination narrowed an attacker's search surface. Replaced both load-bearing references with "internal deployment notes" placeholders per CLAUDE.md "Public-Repo Information Hygiene". The historical disclosure remains in immutable git history at the prior PR refs (cannot be retracted); this PR stops further on-main propagation.
A major release because two long-standing defaults change. Both flips are backed by empirical data; both have explicit opt-out paths.
thinking-block-sanitizev1 is now on by default (#162, #63147, #201). Was opt-in viaCACHE_FIX_THINKING_SANITIZE=onin v3.8.0–v3.9.x. Seven days of prod dogfood (2026-05-29 → 2026-06-05) across 37 sessions: zerocannot be modified400s, cache hit-rate aggregate 94.66% vs. 92.44% baseline (no prefix degradation), sanitize fired on ~35% of sessions with ~800 blocks dropped per day, max 938K context healthy. SetCACHE_FIX_THINKING_SANITIZE=offto explicitly disable. Credit to @yurukusa for the 13E cluster taxonomy and the 22:32 UTC 2026-05-29 synthesis comment on #63147 that made the v2 predicate (cache-fix #171) tractable.- In-process extension hot-reload is now off by default (#196, #198, #200). Was on in v3.x. Set
CACHE_FIX_HOT_RELOAD=onin the proxy's runtime environment (or in the install-service environment if usingcache-fix-proxy install-service) to restore the prior behavior. Off-by-default eliminates the Node ESM stale-import race that silently brokethinking-block-sanitize v2for 17 hours after PR #192's merge — the watcher re-imports an extension whose transitive dependencies are already cached by Node's loader, and Node cannot evict cached transitive modules in-process. Cold starts are unaffected. - A supervisor-level proxy restart is now required after
npm install -g claude-code-cache-fix@4to pick up extension changes. See Upgrading from v3.x for per-platform restart commands. - Embedder note (Bun hosts, DAP-style integrations using
createProxyServer()/startProxy()). v4.0.0 flipsCACHE_FIX_THINKING_SANITIZEfrom default-off to default-on. The v1 omitted-text drop will run on every request body passing through the embedded proxy. If your host depends on the prior no-sanitization behavior (e.g., your downstream code expects emptythinkingblocks to survive the proxy round-trip), setCACHE_FIX_THINKING_SANITIZE=offin the host environment, orprocess.env.CACHE_FIX_THINKING_SANITIZE = "off"in your code at any point before request handling (the mode is read per-request viamodeFromEnv(), not cached at module load). The flip is backed by the same 7-day dogfood data above. See PR #201 and #63147.
thinking-block-sanitizev2 — tools-hash-mismatch drop (opt-in, #171, #192). A new mode of the sanitize extension that detects cross-request tools-surface change via a per-session tools-hash baseline and strips ALL prior-turn signed thinking (boththinkingblocks with non-empty text ANDredacted_thinkingblocks) when the hash flips. Targets yurukusa's 13E (ToolSearch) sub-pattern of anthropics/claude-code#63147, where dynamically-loaded tools mid-conversation invalidate the prior assistant turn's thinking signature and produce a per-turn 400 + retry tax. Opt-in viaCACHE_FIX_THINKING_SANITIZE=v2(a strict superset of=on— v2 mode also runs v1's omitted-text drop). Stays opt-in pending its own prod-dogfood window, now that #196 has closed the silent-load failure mode that prevented v2 from running in prior testing. Newproxy/extensions/signature-surface-hash.mjshelper computes the deterministic 16-char sha256 hash over the canonicalized tools surface./healthextension-load observability (#196, #197). When an extension fails to import — including the Node ESM stale-import race that originally surfaced in #196 — every failure is recorded on the pipeline module and surfaced via/healthas503 + {status:"degraded", failed_extensions:[...], hint:"restart the proxy via your supervisor to recover..."}. Healthy proxies still return200 + {status:"ok"}. Catches load failures within seconds of the bad import instead of leaving the operator to grep the journal. NewgetFailedExtensions()export onproxy/pipeline.mjsfor any other operator-facing tool that wants to surface the same state.
Two upstream-CC-bug workarounds routed through our new cc-triage pipeline: auto-1m-guard (proxy extension, the intercept side) for the auto-1M-context overage case (upstream anthropics/claude-code#64919), and worktree-edit-guard (client-side hook, the boundary-enforcement side) for the worktree parent-checkout corruption case (upstream anthropics/claude-code#59628).
-
auto-1m-guardproxy extension (#179, #185, #186) — addresses CC#64919. Detects thecontext-1m-2025-08-07token on the outboundanthropic-betarequest header and either annotates the session JSON (warn) or removes the token before forwarding (strip), depending on the mode. Three modes viaCACHE_FIX_AUTO_1M_GUARD:off,warn(default — annotation + stderr log line, no request mutation),strip(opt-in — additionally removes the token, defensive against duplicates, rejoins remaining tokens with the CC-canonical,separator). Order 520 (betweenttl-managementandthinking-block-sanitize). Annotation flows throughctx.meta._auto1mGuardwith fieldsauto_1m_detected/auto_1m_action/auto_1m_advice, spread top-level into the per-session JSON bycache-telemetry. Header read is case-insensitive and whitespace-tolerant (mirrorsproxy/extensions/upstream-change-detection.mjs:200-207).Why the proxy, not the CC env var. CC's
CLAUDE_CODE_DISABLE_1M_CONTEXT=1env var disables the entire 1M-context path when it reaches the CC process, but on the VS Code Extension surface it's reportedly unreliable (the extension doesn't always propagate the env var to the spawned CC process). The proxy intercept acts on the outbound wire regardless of which CC launcher produced the request.Binary-walk in the directive established the wire signal. Verified against CC v2.1.148 AND v2.1.161 (same code body, minified identifiers churn): CC sanitizes the
[1m]suffix fromreq.body.modelvia the model sanitizer (sLin v2.1.148 /kJin v2.1.161) at everymessages.createcall site BEFORE the request leaves. So the proxy-visible signal is theanthropic-betaheader token (context-1m-2025-08-07), not the model field. The 1M-beta gate (W2/bZ) keys on/\[1m\]/i.test(model)against the internal model string; the kill switch (xKH/E9H) readsprocess.env.CLAUDE_CODE_DISABLE_1M_CONTEXT. Seedocs/directives/proxy-auto-1m-guard.mdfor the full binary references and a name-translation table for future re-verifications.Out of scope for v1. No subscription-tier classification (cache-fix has no
subscription_tierfield today; the original sketch's "if Pro" conditional is unimplementable without separate infrastructure); the directive replaces it with explicit user opt-in (warndefault,stripexplicit). No[2m]/context-2m-*handling (the model sanitizer covers[2m]but no active gate exists in the binary yet — revisit if 2M ships). No SessionStart hook complement (different surface; out of scope here). -
worktree-edit-guardPreToolUse hook (#182, #183, #184) — addresses CC#59628. New shipped example underhooks/examples/worktree-edit-guard.pyplus an install + behavior doc atdocs/hooks/worktree-edit-guard.mdand ahooks/README.mdlanding page. Independent of the proxy — pure client-side hook that users install by pointing at it from their own~/.claude/settings.json(PreToolUseevent, matcherEdit|Write|MultiEdit|NotebookEdit). Blocks tool calls whose realpath'd target falls outside the active git worktree, addressing the upstream-documented data-loss case where worktree sessions can dirty the parent main checkout's branch with no guardrail.Containment shape. Strict-containment via realpath comparison (rejects any target outside the worktree, including extra writable directories opted in via
--add-dir— the directive blesses this incompatibility as the deliberate tradeoff). Worktree detection is depth-stable: compares the realpaths ofgit rev-parse --git-dirandgit rev-parse --git-common-dir(they match in a regular checkout from any depth and differ inside a linked worktree), so the hook is safe to install globally and is a no-op outside worktrees. Symlink-escape is covered for both existing targets (directrealpath) and not-yet-existing targets (parent-dir realpath catches a symlinked parent). CC'sPreToolUseblocking contract:exit 2with stderr feedback to the agent.Per-tool path field:
file_pathforEdit/Write/MultiEdit,notebook_pathforNotebookEdit. Posture: fail-open on environmental failures (git timeout, malformed stdin, not-in-a-git-repo), fail-closed on protocol-shape mismatches (missing expected path field on an in-scope tool — stderr names the missing field).Real load-bearing bug caught during review. Codex's implementation review flagged that the initial
resolved_target()always used parent-dir realpath + basename, which let an existing symlink-file target resolve back to itself instead of its destination — a silent symlink-escape bypass. Fix usesos.path.lexists(target)to detect whether the target exists (including as a symlink or broken symlink) and realpath the target directly when it does; parent-dir reconstruction is reserved for the not-yet-existing case. -
ctx.meta._auto1mGuardsession-JSON annotation channel (additive).~/.claude/quota-status/sessions/<id>.jsonnow also carriesauto_1m_detected,auto_1m_action, andauto_1m_advicewhen theauto-1m-guardextension fires. Fields are written by the existing single per-session writer (cache-telemetry); existing consumers are unaffected (all use optional reads). Field absent when the extension isoff, the outbound request had nocontext-1m-2025-08-07token, or the request had noanthropic-betaheader at all.
-
tools/manual-compact.sh: Opus summarizer + relaxed recent-turn truncation (#169). The manual-compaction dev tool now defaults toclaude-opus-4-7(wasclaude-sonnet-4-6), overridable viaMANUAL_COMPACT_MODEL, and keeps more per-turn detail in the extract (active turns 2000→8000 chars, working 400→1500, foundational 200→300) for higher-fidelity summaries. Doc adds a troubleshooting note for the oversized-extract / empty-output case (use a[1m]-window model or lower the caps). Includes aLimitationssubsection documenting CC'scleanupPeriodDays-driven transcript sweep (#62272) so manual-compact users planning to keep the on-disk JSONL as a "just in case" backup understand the retention window. -
statusline: round bar tick like fill (#155, schuay).draw_barpreviously rounded the fill but truncated the tick. Withconsumed=15%andelapsed=19.6%that gavefill=2andtick=1, placing the tick inside the filled run — the visual reserved for over-pace. Both are now rounded symmetrically; monotonicity keeps the tick at-or-past the fill wheneverconsumed <= elapsed. Community contribution; thanks @schuay. -
README (zh) refreshed to match the latest English (#178). Translation regenerated and reviewed section-by-section against the English source. Includes the proxy extensions pipeline,
bootstrap-defense, image-guard pipeline, cache breakpoints, microcompact stability, thinking-summaries / session-health / thinking-block-sanitize, and the v3.5.0+ per-session quota-status migration.
927 → 950 (+23): the auto-1m-guard integration + helper suite (23 cases — three-mode integration over the directive's test matrix plus pure-helper unit tests for findBetaHeader, parseBetaTokens, planSanitizeBetaHeader, joinBetaTokens; defensive coverage of duplicate-token strip semantics, single-element header → empty string, case-insensitive header lookup, and whitespace-tolerant detection). The worktree-edit-guard hook tests (20 cases, also new) live as a separate top-level suite — covering in-tree allow, parent-checkout block, totally-out-of-tree block, target-symlink-escape block, parent-symlink-escape block (not-yet-existing case), MultiEdit single-file shape, NotebookEdit notebook_path shape, schema-drift fail-closed cases for both Edit and NotebookEdit, regular-checkout pass-through both at the repo root AND from a nested subdirectory (validates the realpath-equality detection rule), non-git-repo pass-through, deterministic git timeout via PATH shim, relative-path fallback, and out-of-scope Read pass-through.
-
New
hooks/directory ships in the npm tarball. v3.9.0 addshooks/to the npmfilesallowlist so users installing via npm gethooks/examples/worktree-edit-guard.pyandhooks/README.mdlocally. The script's absolute path (used in thecommandfield of thePreToolUsehook config) for an npm install is<npm-prefix>/lib/node_modules/claude-code-cache-fix/hooks/examples/worktree-edit-guard.py— seedocs/hooks/worktree-edit-guard.mdfor the full install snippet. -
First release routed through cc-triage. Both v3.9.0 features came in via a new daily LLM-classified upstream-CC-issue queue (internal tooling; see internal deployment notes), then through standard cache-fix tracking issues and the directive → implementation review workflow. AITL surfaces the issues from the queue; Proxy Builder does the implementation; Codex reviews; Lead + Chris human review for load-bearing items (per CLAUDE.md). Both v3.9.0 PRs were classified load-bearing —
auto-1m-guardfor modifying outbound wire bytes with billing implications,worktree-edit-guardfor being a filesystem-boundary enforcement control whose threat model centers on symlink escape.
The thinking-desync response (upstream anthropics/claude-code#63147): the warn-before half (session-health, #160) and the mitigate half (thinking-block-sanitize, #162), plus the ttl-management thinking-block guard (#157/#159).
-
session-health early-warning extension (#158, #160). A new read-only observation extension (
proxy/extensions/session-health.mjs, order 590) that flags long-running Opus 4.7[1m]sessions approaching the thinking-desync wedge (upstreamanthropics/claude-code#63147) before they die. It never mutates the request/response body and never attempts to repair the desync — it only warns so the operator can retire the session deliberately. This is the warn-before half of the thinking-desync response; mitigation (#162) and offline heal are tracked separately.New per-session JSON fields (additive).
~/.claude/quota-status/sessions/<id>.jsonnow also carriescontext_tokens(latest live context =input + cache_read + cache_creation),thinking_block_count(thinking/redacted_thinkingblocks in the latest request),thinking_block_max(session high-water, carried across proxy restarts),first_seen,request_count, andthinking_desync_risk(ok/warn/high). Fields are written by the existing single per-session writer (cache-telemetry); existing consumers are unaffected (all use optional reads). Counts only — no thinking text or signatures are ever recorded.Token-gated warning.
thinking_desync_riskis computed fromcontext_tokensagainstCACHE_FIX_THINKING_RISK_HIGH_TOKENS(default340000, just under the observed ~382K trip) andCACHE_FIX_THINKING_RISK_WARN_TOKENS(default250000). On first crossing intohigh, a one-time content-free stderr line is emitted. Block-count is recorded but does not yet gate the warning (calibrated fast-follow).CACHE_FIX_THINKING_RISK=offsuppresses the warning signal (stderr line + risk field) while raw count telemetry keeps recording. -
thinking-block-sanitize mitigation extension (#162), opt-in. A new request-path extension (
proxy/extensions/thinking-block-sanitize.mjs, order 550) that drops the omitted (thinking:""+ signature) extended-thinking blocks CC re-sends on history-replay paths, before the request is forwarded — heading off the permanent400 ... thinking blocks ... cannot be modifiedwedge (upstreamanthropics/claude-code#63147). This is the mitigate half (the warn-before half is session-health above). Opt-in: only runs whenCACHE_FIX_THINKING_SANITIZE=on(default off) — it mutates request bodies and full live-coverage validation is pending.Turn-selection rule (empirically resolved). Drops omitted thinking from all prior assistant turns and the latest assistant turn — unless the latest turn is an active tool-continuation (its last block is a
tool_usewith a followingtool_result), where the API requires the signed thinking intact and the proxy must not strip it (that case is uncoverable here — no env var both preserves thinking and avoids the wedge;CLAUDE_CODE_DISABLE_THINKING=1/MAX_THINKING_TOKENS=0stop it only by disabling thinking entirely,DISABLE_INTERLEAVED_THINKING=1does not stop the 400, so the answer there is heal/retire). Never touches non-empty thinking;redacted_thinkingis out of scope for v1 (a full scan of the worst-case wedged transcript found zero). Deterministic and cache-prefix-stable. Emits a per-requestthinking_blocks_droppedcount into the per-session JSON (counts only — never content), via the existingcache-telemetrywriter.
ttl-management: never inject a TTL intothinking/redacted_thinkingblocks (#157).injectTtliterated every block in the request; if acache_control: {type: "ephemeral"}breakpoint landed on a thinking block (possible on Opus 4.7 interleaved-thinking turns), it rewrote the block to addttl, which mutates a signed thinking block — the API rejects that with400 ... thinking blocks ... cannot be modified. The injector now skipsthinking/redacted_thinkingblocks entirely (the chokepoint covers both the system-block and message-block paths). Defensive hardening: this was not the cause of the 2026-05-28 interleaved-thinking incident (that was CC-side,anthropics/claude-code#63172), but it's a real latent mutation path with zero upside to keeping. Regression tests pin the skip and the still-inject-on-non-thinking happy path.
-
bootstrap-defense extended to the env-var-selected GrowthBook prompt-injection surface (#153, #154). Claude Code v2.1.152 (shipped 2026-05-27) added a new consumer pattern over the existing
/api/claude_cli/bootstrapresponse body: in remote-control mode (CLAUDE_CODE_REMOTEset), the env varCLAUDE_CODE_SYSTEM_PROMPT_GB_FEATUREselects a GrowthBook flag key whose cached value is used as the agent's system prompt body. Same delivery channel as v3.7.0'stengu_heron_brookheron-brook surface, new key-selection layer over the same payload. v3.7.0 audited the legacy surface only; v3.7.1 closes the gap by auditing both.New audit-log fields (schema v1 → v2). Each
~/.claude/cache-fix-bootstrap-log.jsonlrecord now carriessurface("bootstrap"|"prompt_injection_gb"),prompt_key(the key read as the prompt source, or null),prompt_value_hash(SHA-256 of the value, first 16 hex chars — never the value itself),remote_mode(whetherCLAUDE_CODE_REMOTEis set), andstripped_keys(which keys allowlist mode removed from the response body, empty otherwise). Existing v1 readers see all v1 fields unchanged; new fields default to null/empty for the legacy no-injection-detected case. Multi-surface responses (both keys present, including env-var-aliases-legacy-key) emit one record per surface, correlated byrequest_id+ timestamp window.New
allowlistmode alongside the existingaudit(default) andblockmodes. SetCACHE_FIX_BOOTSTRAP_MODE=allowlistto deny-by-default for prompt-source-eligible keys, allowlist-overridable viaCACHE_FIX_BOOTSTRAP_ALLOWED_KEYS=comma,separated,list. Default allowlist istengu_heron_brook(the only known-legitimate historical key); passCACHE_FIX_BOOTSTRAP_ALLOWED_KEYS=(explicit empty) for full deny-all. Allowlist mode strips non-allowlisted prompt-source keys from the response body before returning it to CC; other GrowthBook flag keys pass through untouched. Documented as experimental — may need updates if Anthropic adds legitimate prompt-source keys in future CC releases.Default behavior unchanged. v3.7.0 → v3.7.1 is a patch release; existing users running
bootstrap-defensein audit mode get expanded coverage for the new surface, not a new behavior class.blockmode semantics are unchanged (empty 200 from onRequest still defeats both surfaces by preventing any flag map from reaching the on-disk GrowthBook cache).Out of scope for v3.7.1. Stale on-disk GrowthBook cache reuse — if CC reads a flag value cached from a prior bootstrap fetch that didn't pass through this proxy run, v3.7.1 will not emit a fresh audit record for that session. Users wanting belt-and-suspenders should use
blockorallowlistmode, which prevent new injection-class keys from reaching the cache going forward. Granularblockmode (parse-strip-reserialize as the default block) and content-pattern key filtering are deferred to v3.8.0+.
850 → 871 (+21): bootstrap-defense surface-detection suite (cases 1–6 audit-mode surface fires, alias case 3a preserving operator-visibility signal with same-key-different-surface emission); allowlist-mode suite (cases 8–12 plus alias case 13); hash-derivation pin (fixture against silent refactor drift); empty-string env-var truthiness pin (4 cases asserting CLAUDE_CODE_REMOTE="" and CLAUDE_CODE_SYSTEM_PROMPT_GB_FEATURE="" are treated as unset, plus inverse non-empty assertions); integration case 14 in test/proxy-server-bootstrap.test.mjs proving ctx.body mutation flows end-to-end through JSON.stringify(resCtx.body) serialization back to the client; integration case 14b proving audit-mode multi-surface emission produces two correlated records end-to-end through handleBootstrap.
Docker smoke test (docs/release-tests/v3.7.1-docker-smoke-2026-05-27.md): five-section validation of the v3.7.1 build in a node:22-alpine container against a stubbed bootstrap upstream. Covers container boot//health, extension manifest version (SCHEMA_VERSION=2, EXTENSION_VERSION="v3.7.1"), audit-mode single-surface record shape, audit-mode multi-surface emission with shared request_id correlation, block-mode empty-200 with upstream-not-called assertion, and allowlist-mode on-the-wire key stripping. Verdict: GREEN.
-
Bootstrap-channel handling and audit logging (#149, #146). Adds explicit handling for
/api/claude_cli/bootstrapto the proxy router, with audit logging at~/.claude/cache-fix-bootstrap-log.jsonl(5 MB cap,.1rotation). Log records include forward-compatible fields (baseline_hash,anomaly_status,mode,extension_version) that v3.8.0 will populate as the bootstrap-defense extension matures on the pipeline framework.Behavior change for existing cache-fix users. Prior versions routed only
/v1/messagesand/health, returning 404 for any other Anthropic API path including bootstrap. As a result, bootstrap-section content was not previously reaching CC for cache-fix users. v3.7.0 default mode isaudit: bootstrap responses now proxy through to CC and are logged locally for inspection. Users who want to preserve v3.6.2's de-facto block behavior should setCACHE_FIX_BOOTSTRAP_MODE=blockin the proxy environment, which short-circuits the upstream call and returns a 200 with an empty JSON body.Background. Claude Code v2.1.150 added a prompt-section consumer (
nAA()/heron_brook) that reads server-supplied strings from/api/claude_cli/bootstrapand merges them into the agent's behavioral-instructions prompt. We filed the behavior with Anthropic via HackerOne VDP on 2026-05-25; the report was closed as Informative on 2026-05-26, with Anthropic treating TLS as the transport-integrity boundary and declining to add application-layer authenticity checks. This release gives cache-fix users local visibility into bootstrap-channel content (audit mode) and an opt-in path to drop it (block mode). Seedocs/disclosure/heron-brook-2026-05.mdfor the full disposition record.Operational notes.
- Audit mode writes metadata about bootstrap fetches to a local log file. The log never leaves the host. Records are scalar-only by design (no headers, no bodies) — PII discipline is enforced at the writer's call signature.
- Block mode also writes to the audit log (the block event itself is logged); auditability of blocks matters more than log volume.
- Upstream errors on the bootstrap path are also routed through the audit pipeline (
phase: upstream_error_audited). Anomaly-friendly: a DNS-shenanigan or upstream-outage probe leaves a record before the client receives a 502. - Log path is
~/.claude/cache-fix-bootstrap-log.jsonlby default; override withCACHE_FIX_BOOTSTRAP_LOG_PATHif you need to redirect it (used for test isolation; useful for sandboxed deployments). - No statusline signal in v3.7.0 — check the log file directly. The env-flag-detector statusline pattern (#144) will absorb bootstrap-log surfacing in v3.8.0.
- Single-process invariant: the cache-fix proxy is one Node process per host, so the audit writer relies on intra-process serialization. Future changes must preserve this.
- Pipeline framework: this release adds a new pipeline hook for the bootstrap path; v3.8.0's anomaly + baseline + dismissal extension binds to it.
tools/quota-statusline.sh: autoselect d/h vs h/m time format, unify burn-rate warmup gate to 5m (#143). Time-unit autoselect: when the duration is ≥ 1 day, render asNd Hh(e.g.,3d13h,3d0h); below a day, render asHh Mm(e.g.,13h25m). Replaces the prior3d 13h/0d13hshapes — both windows now drop the day token automatically when sub-day. Burn-rate warmup gate is now a unified 5-minute (300s) threshold for both Q5h and Q7d, replacing the prior asymmetric60s/360sper-window gates; the rationale is that a single early call dominates the rate, so the gate is about smoothing the rate-of-change estimate, not about per-window calibration. Named constants (BURN_WARMUP_SEC, time-unitSEC_PER_*etc.) replace bare integer literals in the script. README example tokens updated to the new compact form. Contributed by @schuay — thank you.
831 → 850 (+20): bootstrap-defense unit suite (mode resolution, route-scoping skip, PII discipline grep, audit/block/error-path JSONL shape, log rotation), proxy-server bootstrap integration (audit/block/empty-body stderr-cleanliness, ECONNREFUSED 502 path, non-JSON upstream response), and T16 pinning the new 300s BURN_WARMUP_SEC at 100s elapsed (between the legacy 60s Q5h gate and the new 300s gate — closes the contract gap where T13's 30s elapsed passed under both old and new gate values).
- README: "Recommended CC operational config" section (#139). Documents three
~/.claude/settings.jsonenv vars (CLAUDE_CODE_DISABLE_LEGACY_MODEL_REMAP=1,ANTHROPIC_MODEL,ANTHROPIC_SMALL_FAST_MODEL) that solve adjacent CC problems the proxy can't reach — silent model swap on update, ambiguous model fallback. Plus a caveat onautoCompactWindow=1M(only works on 1M-eligible models) and a footnote onCLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1(strips tool fields outside the canonical four). Findings sourced from @fgrosswig's binary analysis of CC v2.1.91 — public methodology (PowerShell + ASCII string extraction), privately shared punch list. Thank you, @fgrosswig.
- README: removed incorrect "npm provenance" claim from the supply-chain paragraph (#134). Prior README copy stated "npm provenance links each published version to its source commit." Verified against the npm registry: no published release (including v3.6.0 and v3.6.1) carries sigstore provenance attestations — only the default
signaturesfield. The claim has never been factual. Corrected to "Published builds carry npm's default registry signatures; sigstore provenance attestation is not currently published — tracked as a follow-up." No package behavior change; doc-accuracy correction only.
tools/quota-statusline.sh: Q5h/Q7d now render as a quota bar with an elapsed-time tick, plus an exhaust-vs-reset projection (#140). Each window shows a 10-cell bar[███░┃░░░░░]where filled cells are consumed quota and the heavy-vertical tick marks wall-clock elapsed position. Tick in the empty region = under pace; tick inside the fill = burning faster than time. The suffix(exhaust X, reset Y)replaces the previous burn-rate display:exhaustis projected time-to-100% at the current burn rate,resetis wall-clock time until the window rolls over. Whenexhaust < resetthe user is on track to hit 100% before the window resets — the comparison is the actionable signal, which the raw%/minrate didn't convey without mental math. Output line shape changes fromQ5h: 42% (+0.4%/m)toQ5h [████░░░░░┃] 42% (exhaust 0h32m, reset 2h50m); downstream consumers that grepQ5h: N%need to switch toQ5h \[.{10}\] N%. Suffix segments are dropped piecewise when projection isn't meaningful: atpct == 0orpct == 100onlyresetis shown; during the burn-rate warmup (≤60s for Q5h, ≤360s for Q7d)exhaustis held back; a staleresets_atdrops both. The bar uses standard Unicode block characters (█┃░); terminals without Unicode font coverage will need a Unicode-capable monospace font. TTL/hit-rate/PEAK/OVERAGE suffix segments are unchanged. Contributed by @schuay — thank you.
824 → 831 (+7): seven new format-contract tests (T8-T14) pin bar content and the (exhaust X, reset Y) wording for under-pace, over-pace, missing-resets_at, stale-window, fresh-window (pct=0), pre-warmup, and at-cap (pct=100) cases. Anchored against a fixed now via account.json.timestamp so timestamps are deterministic across CI runs.
-
thinking-displayextension: restores Opus 4.7 thinking summaries in non-interactive CC surfaces (#131, closes #130). Anthropic flipped thethinking.displayAPI default to"omitted"on Opus 4.7, and Claude Code's CLI gatesdisplay: "summarized"behind!getIsNonInteractiveSession()— so every CC subprocess spawned with--input-format stream-json(VS Code chat panel, Antigravity panel, SDK,claude --print, etc.) sends a thinking-enabled request withoutdisplay, and the API returns thinking blocks whosethinkingfield is empty plus a multi-KB signature. The UI shows a static "Thinking" stub but no reasoning content. This extension injectsthinking.display = "summarized"at the proxy boundary when the request is onclaude-opus-4-7*,thinking.typeis"enabled"or"adaptive", anddisplayis unset — works on any CC version routed through cache-fix-proxy without waiting on the upstream CLI fix. Upstream root cause / patch proposal in anthropics/claude-code#59844 — credit to @ojura. Default issummarized(default-on for Opus 4.7) after the cache-prefix test on PR #131 measured 0% absolute drop in steady-statecache_readratio (5 sequentialclaude -pcalls per window, baseline vs injected — both windows held 1.000 cache_read ratio from call 2 onward, comfortably inside the ≤5% "preserved" threshold pinned in the PR body before the test ran). Users who want no injection setCACHE_FIX_THINKING_DISPLAY=disabled; users who want explicit thinking suppression setCACHE_FIX_THINKING_DISPLAY=omitted. User opt-out is always preserved — if a request already hasthinking.displayset (either"summarized"or"omitted"), the extension never overwrites. Model-gated regex is intentionally narrow (/^claude-opus-4-7/); Sonnet 4.7 and future versions require explicit verification + a cache-fix bump rather than auto-applying unverified behavior. -
docs/parallel-proxy-test-harness.md: developer test harness for end-to-end extension testing. Documents the pattern used during #131 work: spin up a parallel proxy on:9802from the feature branch, routeclaude -ptraffic through it (bypassing the local wrapper that hardcodes:9801), capture real request bodies via a diagnostic extension, run baseline-vs-injected comparisons against live Anthropic API. The harness surfaced the spec/reality mismatch on #130 (CC v2.1.131 shipsthinking.type: "adaptive", not"enabled"as the upstream issue described) that no unit test would have caught — captured here as a standing pattern for every future extension PR. Six gotchas inlined (~/bin/claudewrapper hardcoding,--verboserequirement forstream-json, env-vars-need-restart, PID-discovery viass -tlnp,pkill -fself-kill risk, adaptive-thinking-is-model-decided).
793 → 824 (+31): 31 new tests for thinking-display covering resolveMode (env values, fallback, garbage rejection), MODEL_REGEX (Opus 4.7 + 1m variant, negative cases for 4.6, Sonnet, future 4.8), shouldInject (all combinations of model × thinking state × display state, including the adaptive type discovered during live test), and onRequest end-to-end (each mode × each body shape, plus the pinned user-opt-out preservation case for both explicit "omitted" and explicit "summarized").
- Embeddable proxy factory:
createProxyServer()+startProxy(options)exported fromclaude-code-cache-fix/proxy/server(#123). Lets Node/Bun hosts run the cache-fix proxy in-process instead of forking a child via thecache-fix-proxybin. The CLI entrypoint (node proxy/server.mjs,cache-fix-proxy server, and the wrapper's child-fork path) is preserved — auto-listen and SIGTERM/SIGINT handlers are now gated behind animport.meta.url === pathToFileURL(process.argv[1]).hrefmain-module check, so library imports have no side effects.package.jsonexportsadds a./proxy/serversubpath; the root entry (./preload.mjs) is unchanged. Adds 4 embeddable tests (factory shape, OS-assigned port, two instances coexisting, port reuse after close). README section added documenting the new API and the "one extension registry per process" constraint. Contributed by @bilby91 (Crunchloop DAP) — thank you, Martín.
startProxy().close()now also closes the file watcher. The initial implementation in #123 captured the http server but discarded the handle returned bystartWatcher(). Embedded hosts withwatch: true(the default) that started/stopped the proxy across lifecycle iterations leaked twofs.watchhandles per cycle. No regression test ships with this fix — verifying thatstartProxy().close()invokes the underlying watcher'sclose()requires either dependency injection on the production API or invasive module-scope inspection ofpipeline.mjsstate. The fix itself is a four-line capture+close inproxy/server.mjs:startProxy()and is verifiable by code review.
cache-telemetry: overage-billing accounts had silent statusline (#121). Accounts on Anthropic overage billing returnanthropic-ratelimit-unified-resetandanthropic-ratelimit-unified-overage-resetinstead of the 5h/7d-specific reset headers. TheparseHeadersguard requiredq5h_reset || q7d_resetand returnednullfor every request on these accounts, socache-telemetrywrote noaccount.jsonor session file and the statusline had no TTL/hit-rate data. Fix: parseunified_resetand widen the guard to accept any reset timestamp. Adds test 6a (overage-only header set → account/session files written,five_hour.pct/seven_day.pctcorrectly 0). Reported and fixed by @TemaThe — thank you.
788 → 789 (+1): test 6a covers the overage-billing header shape end-to-end through onResponseStart and onStreamEvent.
THIRD_PARTY_LICENSES: Apache 2.0 attribution for the NDJSON proxy log schema (#116, closes #115). The schema used bytools/usage-to-dashboard-ndjson.mjs(field names, structure,proxy-YYYY-MM-DD.ndjsonfile naming convention,cache_healthsemantics, andcost_factormethodology) originates from @fgrosswig's claude-usage-dashboard (Apache License 2.0). This release adds the formal Section 4 attribution as aTHIRD_PARTY_LICENSESfile and ensures it ships in the npm tarball via thepackage.jsonfilesarray. cache-fix overall remains MIT-licensed; only the NDJSON schema portion is governed by Apache 2.0. Reported and authored by @fgrosswig; thepackage.jsonpackaging fix was pushed to his branch via maintainer-edits. Thank you, Falk.
tools/usage-to-dashboard-ndjson.mjs: file header acknowledges the Apache 2.0 origin of the NDJSON schema portion (the rest of the file remains under cache-fix's MIT license per the repoLICENSE).
tools/usage-to-dashboard-ndjson.mjs: documented dashboard-integration bridge silently dropped every v:1 usage row (#112). The translator was written for the preload-erausage.jsonlschema (entry.timestamp,entry.q5h_pct/entry.q7d_pctas int 0-100). The proxyusage-logextension introduced in v3.2.0 writes MeterRowSchema v:1 with three field renames (entry.ts,entry.q5h/entry.q7das float 0-1). The translator's entry guardif (!entry.timestamp) return nullsilently dropped every v:1 row, so external dashboards consuming the bridge received zero data from proxy-mode sessions. The integration is documented in our README's Companion Tools section anddocs/dashboard-integration.md— this was a documented-feature regression. Fix: entry guard, quota-header reconstruction,ts_start/ts_endmapping, andreq_idgeneration now accept both schemas via fallback (preload-era field if present, else v:1 field). Backwards-compatible — both formats work, no migration required. Adds 16 regression tests covering both schemas plus parity (req_idis identical for the same logical request expressed in either schema, so dashboards that dedup onreq_idwon't see duplicates from a user upgrading preload→proxy). Reported by @TomTheMenace with a tested patch already in the issue body — thank you.
772 → 788 (+16): regression coverage for the dashboard-integration translator (#112) — preload-era and v:1 entry guard, ts_start/ts_end mapping, quota-header reconstruction with the legacy-takes-precedence rule, deterministic req_id, and full record parity between schemas.
tools/quota-statusline.sh: shell injection via Python triple-quoted literal (#108). The v3.5.0 statusline rewrite interpolated CC's hook stdin payload directly into a Python triple-quoted string (json.loads('''$input''')). A'''byte sequence anywhere in the payload closed the literal early and let the following bytes execute as Python in the user's CC process. Because CC's hook payload reflects user-controlled paths (cwd,workspace.current_dir,workspace.project_dir,transcript_path) and apostrophes are legal in filesystem paths, a hostile directory name on disk (planted viagit clone, archive extraction, npm package, etc.) could trigger arbitrary local code execution at the user's privilege every time CC redrew the statusline. Severity: local code execution, persistent re-fire on every statusline tick, no user interaction beyondcd-ing into the hostile path. Fix: capture stdin in bash,export CC_INPUT, and pipe the Python source through a single-quoted heredoc (<<'PYEOF') which disables ALL bash interpolation in the body. Python now reads the JSON viaos.environ.get('CC_INPUT'), where the bytes are inert at every layer. Adds T6 + T7 regression tests that drive the exact'''+__import__('os').system(...)+'''pattern against the script under a tmpdir-rootedHOMEand assert the sentinel file is never created. Reported by @schuay (Jakob Linke) in #108 — thank you for the responsible disclosure.
735 → 737 (+2): T6 and T7 regression coverage for the #108 injection vector — payload in session_id and in non-session_id user-controlled fields (cwd, workspace.current_dir, transcript_path).
cache-telemetry: session-id headers (x-claude-code-session-idand the legacy fallbacks) live on the request, not the response. The v3.5.0 implementation read them fromctx.headersinonResponseStart— but that ctx carries response headers, and Anthropic doesn't echo session-id back. Net effect on multi-agent hosts running v3.5.0: every per-session file landed atsessions/unknown.jsonwithsession_id: null, defeating the whole point of the per-session split. Captured production failure on immediately after v3.5.0 rollout. Fix moves session-id resolution into a newonRequesthook (request headers);onStreamEventreads fromctx.meta._sessionIdas before. The proxy server passes the samemetaobject throughonRequest → onResponseStart → onStreamEvent, so the threading works end-to-end. Adds two regression tests that drive request and response headers separately to prevent recurrence.
733 → 735 (+2): regression coverage for the request-vs-response ctx split (capture from request, fallback when absent).
- Breaking (path change):
~/.claude/quota-status.json(single global file) replaced with~/.claude/quota-status/account.json(account-global quota fields: Q5h/Q7d, status, overage) plus~/.claude/quota-status/sessions/<filename>.json(per-session cache fields: TTL tier, hit rate, cache_creation/read).<filename>is derived from the request'sx-claude-code-session-idheader via a deterministic safe-name rule (UUIDs and similar safe ids pass through; malformed inputs are mapped toinv-<sha256-prefix>). Multi-agent users no longer see cross-session contamination — each session's cache state is attributed correctly. Custom statusline scripts that read the old global path must update to the new layout; the shippedtools/quota-statusline.shhas been migrated. The legacy file is auto-deleted on first request after upgrade. Per-session files older thanCACHE_FIX_QUOTA_STATUS_TTL_DAYS(default7) are swept on write. If you have your own consumer ofquota-status.json, see the new Migration: v3.4.x → v3.5.0+ section in the README for the try-new-fall-back-to-legacy pattern. (#105, closes #104)
microcompact-stability: session-id fallback chain now includesx-claude-code-session-id(the canonical CC header). Was previously checking onlymeta.session_id,x-session-id, andx-anthropic-session-id, returning null hashed-session-id for most CC requests in the wild and weakening per-session attribution in microcompact diagnostics. (#105)
698 → 733 (+35): new tests for the sessionFilename rule, file-write happy paths and fallbacks, atomic write contract, legacy-file cleanup (one-shot per process), TTL sweep behavior + throttling + env-override, microcompact session-id fallback chain precedence, and pipeline integration covering happy path, two-session interleaving, and path-traversal safety. Plus T1–T5 statusline smoke tests covering UUID happy path, missing session_id (file present and absent), warming-state, all-files-missing clean exit, and malformed session_id reading the hashed filename.
- New extension
messages-cache-breakpoint(order 410, opt-in viaCACHE_FIX_INJECT_MESSAGES_BREAKPOINT=1) — injects the missing breakpoint #3cache_controlmarker at the boundary between Claude Code's auto-injectedmessages[0]blocks (hooks, skills, project CLAUDE.md, deferred-tools, MCP server descriptions) and the first real user content. Anthropic's prompt cache supports up to 4 markers per request; CC currently uses 3, leaving the auto-injected span uncached. Conservative: skips on 0 markers (non-CC baseline) and refuses at 4 markers (would 400 the request). Five-kind boundary detection with fail-open classification. AddsCACHE_FIX_DUMP_MESSAGES_HEAD=<path>diagnostic dump for fixture sourcing. (#90, closes #12; @wadabum's 4-breakpoint analysis at anthropics/claude-code#47098) - New extension
microcompact-stability(order 350) — Phase 1 of a two-phase fix for the cache-prefix invalidation observed when CC'stime_based_microcompactwrites a sentinel string that differs byte-wise between firings. Phase 1 ships diagnostic capture (CACHE_FIX_DUMP_MICROCOMPACT=<path>) and opt-in normalization (CACHE_FIX_NORMALIZE_MICROCOMPACT=1); both default off pending production data. Default canonical form[Old tool result content cleared]overridable viaCACHE_FIX_MICROCOMPACT_NORMALIZED=<text>/CACHE_FIX_MICROCOMPACT_SENTINEL_PATTERN=<regex>. Phase 2 (snapshot-and-restore) deferred to a future release. (#91, closes #36) - New extension
ttl-tier-detect(order 75, default-enabled, no env var required) — detectscache_control.ttl="5m"markers in the incoming payload before downstream extensions strip them, recording the result onctx.meta._ttlTier. Pure detection, no mutation. Ports the in-payload tier-detection frompreload.mjs:1815-1828. (#100, closes #97; @vmfarms surfaced this)
ttl-managementnow consumesctx.meta._ttlTierand auto-upgrades injected TTL: when the incoming payload carries anyttl="5m"marker, all injectedcache_controlblocks getttl="5m", even ifCACHE_FIX_TTL_MAIN/CACHE_FIX_TTL_SUBAGENTis set to1h. Env valuenonestill suppresses injection entirely. (#100)
identity-normalization: theSessionStart:resume → :startuprewrite was a silent no-op — the marker constant matched the post-rewrite output instead of the input, so users on proxy mode silently lost the resume-block stabilization that preload mode performs correctly. Single-character fix; new tests mirror preload-side coverage. (#99, closes #96; @vmfarms surfaced this)image-strip: legacy[image-strip]and v3.3.0[image-guard]operational stderr summaries fired on every request that did observable work, regardless ofCACHE_FIX_DEBUG. Both now requireCACHE_FIX_DEBUG=1. ThePRESERVE_DETAIL-without-GUARDmisconfiguration warning stays unconditional. (#99, closes #98; @vmfarms surfaced this)
- Author info and blog-link references migrated to vsits.co. (#95)
- New canonical release procedure documented at
docs/release-workflow.md. (#101)
597 → 698 (+101): new extension tests for messages-cache-breakpoint, microcompact-stability, ttl-tier-detect, plus pipeline-level integration tests for tier-detection and new tests for the two identity-normalization and image-strip bug fixes.
image-guard pipeline (#87, closes design discussion in #87 thread):
Replaces v3.2.1's static CACHE_FIX_IMAGE_MAX_DIM with a conditional pipeline that mirrors Anthropic's actual image rules: the per-image dimension ceiling depends on image count (2000 px when count > 20, else 8000 px), the API enforces a 32 MB request body cap independently, and current-generation models accept up to 100 images per request. The new pipeline addresses all three axes; MAX_DIM only addressed the dimension axis with a single static value that overcorrected for ≤20-image requests.
Five passes, all gated by a single top-level env var (CACHE_FIX_IMAGE_GUARD=1):
| Pass | Trigger | Action |
|---|---|---|
| Pass 0 (legacy back-compat) | CACHE_FIX_IMAGE_KEEP_LAST=N set |
Strip tool_result images from user messages older than N most recent |
| Pass 3 (opt-in) | CACHE_FIX_IMAGE_PRESERVE_DETAIL=1 AND long edge > model native cap |
Lanczos resize via sharp to native cap (2576 px Opus 4.7, 1568 px otherwise), preserve aspect ratio and media type |
| Pass 1 | long edge > active rejection cap | Strip with forensic placeholder. Cap = MAX_DIM if set, else 2000 (count > 20) or 8000 (count ≤ 20) |
| Pass 2 | request body bytes > CACHE_FIX_IMAGE_REQUEST_SIZE_MAX (default 30 MB) |
Drop oldest images until under budget |
| Count cap | image count > CACHE_FIX_IMAGE_COUNT_MAX (default 100) |
Drop oldest images down to cap |
Execution order: Pass 0 → Pass 3 → Pass 1 → Pass 2 → count cap. Each pass is independent — Pass 1 never resizes; Pass 3 never strips. README's precedence matrix documents every supported env-var combination.
Optional sharp peer dependency. Pass 3 requires sharp for Lanczos resize. Declared in peerDependenciesMeta only (not peerDependencies) — users who don't want it pay nothing. If sharp is missing, Pass 3 logs library_missing and skips; Passes 0/1/2 + count cap still run.
Telemetry. New ctx.meta.imageGuardStats carries the full counter set (counts + bytes + estimated tokens + library_missing flag). One stderr line per processed request when the pipeline did anything observable.
New env vars:
CACHE_FIX_IMAGE_GUARD=1— top-level pipeline gateCACHE_FIX_IMAGE_PRESERVE_DETAIL=1— enable Pass 3 Lanczos resize viasharpCACHE_FIX_IMAGE_REQUEST_SIZE_MAX=<bytes>— Pass 2 byte budget (default 31457280 = 30 MB)CACHE_FIX_IMAGE_COUNT_MAX=<n>— hard image-count cap (default 100; legacy Claude 1/2.x/Instant users can set 600)
Back-compat. All v3.2.1 legacy paths (CACHE_FIX_IMAGE_KEEP_LAST only, CACHE_FIX_IMAGE_MAX_DIM only, both together) continue to work exactly as before — no migration required for existing users.
Tests: 553 → 597 (44 new in proxy-image-guard.test.mjs, covering activation, every Pass, count cap, all 10 precedence-matrix rows, telemetry shape, sharp-unavailable + sharp-throws fallbacks, Pass 1 stderr emission, post-count-cap byte recompute). Pass 3 sharp tests use injected mocks — no real sharp install required to run the suite.
Reviewer dance: Codex implementation review found 2 blockers + 1 telemetry-drift note; all addressed in commit 91017e8. Final approval at commit 9983d6a. Both gates met (approved-by-lead + approved-by-codex-agent) before merge.
Oversized-image guard for image-strip (#84, requested by @X-15):
New CACHE_FIX_IMAGE_MAX_DIM=<pixels> env var on the existing image-strip extension. When an image's pixel dimensions exceed the cap (Anthropic's per-image dimension ceiling for many-image requests is 2000px), the image is replaced with a forensic placeholder noting the original dimensions and tool_use_id. Covers both user-message direct images and tool_result-nested images. Pure-JS PNG and JPEG header parsing in new proxy/image-dimensions.mjs — no native dependencies.
Composes with the existing CACHE_FIX_IMAGE_KEEP_LAST (count axis): when both are set, KEEP_LAST runs first (drops images from old messages), then MAX_DIM runs on whatever survives (caps the size of the kept ones). Common triggers for the dimension axis: hi-res manuscript scans, retina screenshots, photos at full resolution.
Tests: 526 → 553 (27 new — 16 in proxy-image-dimensions.test.mjs covering synthesized PNG/JPEG headers, 11 in proxy-image-strip.test.mjs covering MAX_DIM behavior, fail-open semantics, and KEEP_LAST + MAX_DIM composition).
No behavior change for users not setting CACHE_FIX_IMAGE_MAX_DIM. No migration required.
Three new opt-in extensions plus a usage-log rewrite that aligns the proxy's per-call JSONL with claude-code-meter's strict validator.
overage-warning extension (#79, closes #47) — opt-in via CACHE_FIX_OVERAGE_WARNING=1:
When Anthropic's response headers indicate the user is approaching or has crossed the overage threshold (anthropic-ratelimit-unified-status: allowed_warning|throttled plus a non-empty anthropic-ratelimit-unified-7d-surpassed-threshold), emit a one-time-per-threshold-per-Q5h-window warning to stderr AND append a structured record to ~/.claude/overage-warnings.jsonl. Carries a 15-minute rolling sample window to project minutes-to-100% with a coarse cost-per-hour estimate (labeled coarse everywhere — the precise per-tier cost engine is a v3.3.0 follow-up). Single emission per response guaranteed by an emitted flag on ctx.meta. Cross-response dedup keyed by (threshold, q5h_resets_at). New shared rate constant in proxy/rates.mjs.
upstream-change-detection extension (#80, closes #39) — opt-in via CACHE_FIX_UPSTREAM_DETECTION=1:
Read-only structural fingerprinter that detects when CC ships updates that change /v1/messages request shape (cache_control marker count, system block layout, tools list, system-reminder patterns, beta headers). Per-namespace baseline persists across proxy restarts at ~/.claude/upstream-baseline.json (atomic tmp + rename with unique suffix). Events appended to ~/.claude/upstream-changes.jsonl. Mechanically content-free: every persisted field is a count, position, boolean, bucket label, or hash of stable identifiers. Allowlist matches stored as hash-of-sorted-indices, never the matched text. Unknown-marker / unknown-pattern detection records ONLY a boolean. Tested with a "secret string" planted throughout a request body — never appears in the fingerprint.
usage-log rewritten to MeterRowSchema v:1 (#81, closes #70):
The proxy's ~/.claude/usage.jsonl now emits exactly the 29-field record shape that claude-code-meter's strict z.strictObject({ v: z.literal(1), ... }) validator expects. The wire format is now the cross-repo contract — claude-meter v0.4.0+ tails the proxy's JSONL via claude-meter ingest --watch, validates strictly, and persists into the local store the existing analyze/share/status/history/rates already read from. Breaking change for the usage-log row format — old 9-field rows (with peak_hour) in any pre-existing usage.jsonl files will fail claude-meter's strict validator and be skipped on the reader side. peak_hour is no longer in the wire format (recomputable from ts if needed). org_id hashed with sha256(raw).digest("hex").slice(0, 16) — bit-exact match with claude-meter's algorithm, never raw. Activation pattern unchanged: opt-in via extensions.json entry, CACHE_FIX_USAGE_LOG is path override only.
Cross-repo release ordering: cache-fix v3.2.0 ships first. claude-meter v0.4.0 follows, declaring claude-code-cache-fix >= 3.2.0 as its supported producer. The two packages are NOT independently shippable for the proxy-mode ingestion path.
Tests: 465 → 512+ (47+ new). No migration required for proxy or its other extensions.
cache-fix-proxy install-service subcommand (#73, closes #48):
- New CLI dispatch supports
install-service(systemd on Linux, launchd on macOS),uninstall-service,server(run just the proxy in foreground for ExecStart), andhelp. - Existing
cache-fix-proxyno-subcommand wrapper behavior is unchanged (back-compat). - Refuses to overwrite existing config without
--force. Picks upCACHE_FIX_PROXY_PORT,CACHE_FIX_PROXY_UPSTREAM,CACHE_FIX_DEBUGfrom the env at install time. - Templates ship in new
templates/directory.
Healthcheck companion for proxy auto-recovery (#75):
After the 2026-04-25 incident where the proxy was stopped by an unidentified caller during the Anthropic outage and stayed down for ~10 hours (Restart=on-failure doesn't fire on clean stops), install-service now also drops a healthcheck companion on Linux:
cache-fix-proxy-healthcheck.service— oneshot that doescurl -fs http://127.0.0.1:<port>/healthandsystemctl --user start cache-fix-proxy.serviceif the probe failscache-fix-proxy-healthcheck.timer— fires the oneshot 30s after boot then every 2 minutes (AccuracySec=15s)uninstall-servicestops the timer FIRST, then the proxy, then removes all three files
Recovery within 2 minutes from any stop cause: clean stop, crash, OOM, an external systemctl stop. macOS doesn't need it — launchd's KeepAlive already auto-restarts on any exit.
Hardening + security:
- Port string is now validated before being interpolated into the healthcheck shell command. A hostile
CACHE_FIX_PROXY_PORTvalue (with shell metacharacters) would have allowed command injection; rejected with a clear error message now. - Symmetric existence check on the healthcheck pair: refuses overwrite if either the service file OR the timer file exists (caught case where one was a half-installed stale artifact).
- Half-install rollback: if the healthcheck install throws after the main unit is written, the main unit is removed so users aren't left in a partial state.
New doc: docs/security-hardening.md (#74):
Honest assessment of the trust model around running CC + cache-fix proxy. Ranked threat surface, practical mitigations, what we explicitly DON'T defend against. Includes the proposed dangerous-command filter as a future v3.2.0 candidate, and audit-trail enablement docs (systemd user manager debug logging) for forensic recovery.
Tests: 433 → 465 (32 new). No breaking changes. No migration required.
New proxy extensions (drop-in, behavior described inline):
prefix-diff(opt-in viaCACHE_FIX_PREFIXDIFF=1) — pure diagnostic. On every request, snapshots a small projection of the prefix (system prompt + tools + first 5 messages) to~/.claude/cache-fix-snapshots/<key>-last.json. If a prior snapshot exists and content differs, also writes<key>-diff.jsonand emits a one-line stderr summary. Atomic writes; per-call diff (no boot-flag gating). Closes #59 item 9. (#65)deferred-tools-restore(defaults ON; opt out viaCACHE_FIX_SKIP_DEFERRED_TOOLS_RESTORE=1) — preserves cache prefix across the MCP-reconnect race. Onclaude --continue, if MCP servers haven't reconnected before the first post-resume request, the deferred-tools attachment block atmsg[0]shrinks dramatically and busts the cache at the very top (entire ~940K prompt re-caches). This extension persists the clean form of the block and substitutes it on subsequent shrunken requests, with strict downgrade guard (snapshot must be strictly longer than current). Snapshot keyed on the cwd parsed from CC's# Environmentsection in the system prompt — line-based section parser with ambiguity guard fails open on parse failure. Closes #59 item 6. (#66)
Default config update (#69):
- Three pre-existing extensions now enabled in the default
extensions.json:smoosh-split(order 320) — peels system-reminders out oftool_result.contentinto standalone blockscontent-strip(order 330) — removes per-turn bookkeeping reminders (Token usage:,Output tokens —, idle-tool nudges)tool-input-normalize(order 340) — normalizes tool input fields for cache-stable JSON serialization
- Triggered by a real-world cache-miss event: a 606K-message context with a warmer running dropped to 5.9% hit rate; recovered to 99.9% within ~2 calls after enabling these. They were Codex-reviewed and merged days ago but had remained dormant.
Issue #59 closed (10/10 items resolved): #65 + #66 are the last two ports; item 10 (git-status strip) intentionally not ported because the proxy can't reach the system prompt before CC composes it. README updated to document the technical reason and point users at the native CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1 flag.
Docs:
- README + ko/zh translations: removed
claude-code-metersharing references — the integration loaded viaNODE_OPTIONSwhich CC v2.1.113+ ignores (Bun binary). Tracked in #70 for future refactor. (#71) - TRACKED_ISSUES.md backfilled with Apr 23 activity (v3.0.3/4/5 ship notes, three filed CC issues, three new contributor entries). (#68)
docs/deferred/proxy-session-serializer.md— preserves the Phase 3b session-serializer design as a deferred reference. Tracked in #67. (#68)
Tests: 391 → 433 (added 42 for deferred-tools-restore, 25 for prefix-diff).
No breaking changes. No migration required.
CHANGELOG entries were not added for the v3.x patch series at the time. Release notes for each are on GitHub:
- v3.0.0 — local proxy with hot-reloadable extension pipeline
- v3.0.1 — README restructure, bin entry fix
- v3.0.2 — Windows proxy fix + preload empty-content guard
- v3.0.3 — corporate proxy support, updated translations
- v3.0.4 — fix proxy telemetry:
quota-status.jsonwas never written - v3.0.5 — fix status bar reading stale data
- BUGFIX:
manual-compact.shpath conversion failed on directories with underscores — CC normalizes underscores to hyphens in project paths (e.g.kanfei_test→kanfei-test). The script now handles this. Also improved output to show the exact copy-paste message with the real session ID.
16 total cache-stability fixes. 163 tests.
- BUGFIX: TTL ordering violation causes 400 error at Q5h=100% — When the user's quota hit 100%, CC places
ttl: "5m"markers. Our interceptor then addedttl: "1h"markers on other blocks, violating Anthropic's ordering constraint (1h cannot follow 5m in tools→system→messages order). Fix: detect existing TTL tier from the payload before any extension runs. If any block hasttl: "5m", all injected markers (TTL injection,cache_control_normalize,cache_control_sticky) now use5mto match. Reported by @cowwoc (#44).
16 total cache-stability fixes. 163 tests.
- New tool:
manual-compact.sh— Manual compaction for sessions using the 1M context hack (DISABLE_COMPACT=1). Extracts conversation from JSONL, weights recent turns heavily for active-work fidelity, summarizes via Claude Sonnet. Supports project directory auto-detection with confirmation prompt, and optional user context file for known gaps. Tested at 95% active-work fidelity. Seetools/MANUAL-COMPACT.md. - Development workflow — Added formal PR review process, agent identification requirement, label policy, and cross-LLM review workflow to CLAUDE.md.
- TRACKED_ISSUES.md — Updated with v2.1.112/113 context, new issues (#35, #36, #39, #40, #41, #50083), media coverage section, and new contributors (deafsquad, wadabum, cowwoc, stellaraccident).
16 total cache-stability fixes. 162 tests.
- BUGFIX:
cache_control_stickystill exceeded 4-marker limit on CC v2.1.112 — v2.0.2 reducedMAX_POSITIONSfrom 3→2 assuming CC uses exactly 2 markers (1 system + 1 messages). CC v2.1.112 uses 3 markers in some configurations, so 2 sticky + 3 CC = 5, still exceeding the hard limit. Fix: count all existingcache_controlmarkers across the full body (system + messages) before adding sticky markers, and cap the total at 4. No more assumptions about CC's marker budget. Caused400 invalid_request_errorin production.
16 total cache-stability fixes. 162 tests.
- BUGFIX:
cache_control_stickyexceeded Anthropic's 4-marker limit — ReducedMAX_POSITIONSfrom 3 to 2. With 1 system marker + 1 canonical fromcache_control_normalize+ 3 historical = 5, exceeding Anthropic's hard limit of 4cache_controlblocks per request. Caused400 invalid_request_erroron sessions with enough history to fill all 3 slots. Now: 1 system + 1 canonical + 2 historical = 4.
cache_control_sticky— Preserves historicalcache_controlmarker positions across turns. CC maintains one user-side marker at a time, dropping previous positions (~43 bytes of JSON framing per dropped position). On long sessions this causes tail-of-message byte drift that invalidates downstream cached blocks. This extension tracks up to 2 historical marker positions by stable message hash and reinstates them on subsequent turns (2 historical + 1 canonical from normalize + 1 system = 4, Anthropic's hard limit). Runs aftercache_control_normalize. Credit: @deafsquad (PR #33).
16 total cache-stability fixes. 160 tests.
Major release — 7 new cache-stability fixes, expanding the interceptor from 8 fixes to 15. Combined stack reduces first-request cache creation by up to 99.8% on affected accounts (940K → 1.7K tokens measured by @deafsquad). Confirmed compatible with CC v2.1.112 and Opus 4.7.
smoosh_split— Universal un-smoosh: peels any trailing<system-reminder>content out oftool_result.contentstrings back into standalone text blocks. Reverses CC'ssmooshSystemReminderSiblingsfolding that causes per-turn byte drift in tool results. Defaults ON. Credit: @deafsquad (PR #26).session_start_normalize— RewritesSessionStart:resume→:startup, strips<session-id>andLast active:timestamps that differ between startup and resume, eliminating content drift atmessages[0]block 0. Credit: @deafsquad (PR #27). Targets anthropics/claude-code#43657.continue_trailer_strip— Removes the"Continue from where you left off."text block CC injects on--continuethat changes the prefix shape vs a normal turn. Credit: @deafsquad (PR #28).deferred_tools_restore— Snapshots the MCP deferred-tools block and restores it on reconnect race, preventing cache bust when MCP disconnects and reconnects mid-session with different content. Credit: @deafsquad (PR #29).reminder_strip— Drops Token usage / USD budget / output tokens / TodoWrite / turn-counter bookkeeping<system-reminder>blocks that change every turn. Credit: @deafsquad (PR #30).cache_control_normalize— Pins thecache_controlmarker at a canonical position to stop per-turn drift when CC moves the marker between blocks. Credit: @deafsquad (PR #31).tool_use_input_normalize— Strips non-schema keys fromtool_use.inputand canonicalizes key order to schema declaration order. CC's serialization of pasttool_useblocks can drift between turns when the caller passes extra fields not ininput_schema.properties— a 2334-byte drift on a single block caused a 620K-token cache miss. New miss class identified live on 2026-04-17. Credit: @deafsquad (PR #32).
smoosh_normalize— Pattern-based normalization of 4 known dynamic system-reminder values (token_usage, budget_usd, output_token_usage, todo_reminder) in both smooshed and unsmooshed form. Opt-in viaCACHE_FIX_NORMALIZE_SMOOSH=1.cwd_normalize— Replaces volatile CWD and path references in system prompt with stable placeholders for cross-worktree cache reuse. Opt-in viaCACHE_FIX_NORMALIZE_CWD=1. Credit: @wadabum for the architectural analysis (anthropics/claude-code#48236).
Metered data shows Opus 4.7 burns Q5h at ~2.4x the rate of 4.6 due to invisible adaptive thinking tokens not reported in the API usage response. Workaround: CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1 (may reduce quality). See Discussion #25.
This release adds @deafsquad as contributor #10 — source-level function attribution of the resume scatter bug, OTEL telemetry discovery, and 7 PRs (#26-32) providing universal cache-stability coverage.
- Fingerprint verification fix for CC v2.1.108+ — CC v2.1.108 changed fingerprint computation to skip
<system-reminder>blocks via anisMetafilter. The safety check now tries both the new extraction method (v2.1.108+) and the legacy method, keeping fingerprint stabilization working across CC versions.CACHE_FIX_SKIP_FINGERPRINT=1workaround is no longer needed. Credit: @ArkNill (PR #21). - Korean README — Full setup and usage guide in Korean (README.ko.md). Credit: @ArkNill (PR #22).
Security transparency release.
- Postinstall security notice — On
npm install, displays a clear notice that the interceptor has full read/write access to API requests, confirms all telemetry is local-only, and links to source and independent audit. - First-run security log — On first API call, logs the security posture to the debug log alongside the health status line.
- Security Model section in README — Moved to top of README. Documents the MITM position, what the interceptor does and does not do, supply chain profile, and links the independent audit by @TheAuditorTool.
- Confirmed through v2.1.107 — salt and fingerprint indices unchanged.
/clearartifact stripping — Removes<local-command-caveat>,<command-name>, and<local-command-stdout>blocks that bleed intomessages[0]after/clear, breaking prefix cache match vs a fresh session. Credit: @wadabum (anthropics/claude-code#47756).- Status line fallback to
quota-status.json—quota-statusline.shnow works withoutclaude-code-meterinstalled by reading quota data from the interceptor'squota-status.json. Fixes #18. Credit: @dmurat. - VS Code extension — VSIX extension available for one-click activation. Auto-configures
claudeProcessWrapper. No manual wrapper scripts or C compilation needed. Credit: @JEONG-JIWOO, @X-15 (#16). Download: GitHub Releases. - README: VS Code section rewritten — VSIX as Option A (recommended), manual wrapper as Option B. Documents
claudeCode.claudeProcessWrapperas the correct integration path.
- Windows: URL-encode npm root in
claude-fixed.bat— FixesERR_MODULE_NOT_FOUNDon default Windows Node.js installs where npm root contains spaces (e.g.C:\Program Files\nodejs\node_modules). Uses PowerShell[System.Uri]::EscapeUriStringto encode the path; no-op on space-free paths. Credit: @beekamai (PR #17).
Cache-busting mitigation, configurable TTL, and diagnostic tooling.
- Git-status stripping (#11) — Opt-in removal of volatile
gitStatussection from system prompt. CC injects live git status (branch, changed files, recent commits) that changes on every file edit, busting the entire prefix cache. SetCACHE_FIX_STRIP_GIT_STATUS=1to replace with a stable placeholder. The model can still rungit statusvia Bash when it needs context. Kill switch:CACHE_FIX_SKIP_GIT_STATUS=1. - Configurable TTL per request type (#14) — TTL injection now distinguishes main-thread from subagent requests.
CACHE_FIX_TTL_MAINandCACHE_FIX_TTL_SUBAGENTaccept1h(default),5m, ornone(pass-through). Subagent detection reuses the Agent SDK identity string fromsystem[1]. Users on API keys or customANTHROPIC_BASE_URLcan now control TTL per call type. - Cache breakpoint dump (#12) — Diagnostic env var
CACHE_FIX_DUMP_BREAKPOINTS=<path>writes the fullcache_controlbreakpoint structure (system blocks + message blocks) to a JSON file. Maps breakpoint positions, types, TTLs, and content previews. Used to investigate the missing breakpoint #3 (skills/CLAUDE.md) identified by @wadabum. - Cost-report tier fix (#7) —
cost-report.mjsnow correctly assigns cache creation tokens to the 1h write rate whenephemeral_1h_input_tokens > 0. Previously all creation was assumed 5m when the ephemeral breakdown fields were zero, understating cost for 1h-tier sessions.
- nvm-compatible wrapper script — README wrapper now uses
npm root -gfor dynamic path resolution instead of hardcoded$HOME/.npm-global. Fixes setup for nvm, volta, and other Node version managers. Adds existence check for the interceptor module. Credit: @arjansingh (PR #15).
Safety, lifecycle management, and self-deprecation features. Merges @thepiper18's hardening PR (#8) — 28 new tests bringing the suite to 75.
- Fingerprint round-trip safety check (P0) — Before rewriting
cc_version, verifies our salt/indices reproduce the fingerprint CC sent. If verification fails (CC changed its algorithm), the rewrite is skipped automatically. The interceptor can never make cache performance worse than stock CC. - Master kill switch + per-fix toggles —
CACHE_FIX_DISABLED=1disables all bug fixes while keeping monitoring + optimizations active. Per-fix:CACHE_FIX_SKIP_{RELOCATE,FINGERPRINT,TOOL_SORT,TTL,IDENTITY}. - Persistent effectiveness stats —
~/.claude/cache-fix-stats.jsontracks per-fix applied/skipped/safetyBlocked counts with 30-day auto-prune and atomic writes. - Startup health status line — On first API call, logs per-fix status:
active(2h ago),dormant(5 clean sessions),safety-blocked(Nx),waiting. Includes advisory messages for dormant fixes. - Cache regression detector — In-memory ring buffer tracking
cache_readratio. Warns if ratio drops below 50% across 5+ consecutive calls — especially useful when fixes are disabled and CC regresses. - Portuguese guide (
docs/guia-pt-br.md) — Full setup and usage guide in Portuguese. Credit: @thepiper18. - "Graduating from Fixes" + "Safety" README sections — Documents the three-purpose lifecycle model (bug fixes / monitoring / optimizations) and the fail-safe design guarantee.
- Status line for real-time quota/TTL warnings — Ships
tools/quota-statusline.sh, a Claude Code status line script that displays live Q5h%, Q7d%, burn rates, TTL tier, cache hit rate, peak-hour flag, and overage status. When the server downgrades to 5m TTL at Q5h ≥ 100% (Layer 2 quota-aware downgrade), the status line showsTTL:5min red — a visible "stop and wait" signal that prevents users from power-driving through overage and compounding the drain. Setup: copy the script to~/.claude/hooks/and add"statusLine": { "command": "~/.claude/hooks/quota-statusline.sh" }to~/.claude/settings.json. - README: "Status line — quota warnings in real time" — New section with feature list, setup instructions, and explanation of why TTL visibility matters for Layer 2 behavior.
- Windows support — Added
claude-fixed.batwrapper for Windows users whereNODE_OPTIONS="--import ..."doesn't work. Dynamically resolves npm global root, constructsfile:///URL with forward-slash conversion, launches Claude Code with the interceptor active. Credit: @TomTheMenace. - README: Windows setup guide — Step-by-step instructions for Windows users alongside the existing Linux/macOS wrapper, alias, and direct-invocation options.
- Contributors: @TomTheMenace — First Windows platform validation: 7.5-hour, 536-call Opus 4.6 session with 98.4% cache hit rate. 81% of calls had fingerprint instability corrected by the interceptor. Contributed the
.batwrapper.
Investigation release — cross-version regression analysis, interop with @fgrosswig's claude-usage-dashboard, and diagnostic tooling for per-version tool-schema drift.
CACHE_FIX_DUMP_TOOLSdiagnostic hook — Env-gated dump of the outgoingtoolsarray to a JSON file, recording per-tool name, description, schema size, and total serialized size. Used during the 2026-04-11 cross-version regression investigation to identify that Claude Code v2.1.101's +7,207 character tool-schema growth is 92% attributable to two new tools (MonitorandScheduleWakeup) shipped in that release. Inert unlessCACHE_FIX_DUMP_TOOLS=<path>is set.- Full
anthropic-*response header capture — Widened the response header capture inpreload.mjsfrom specific unified-ratelimit headers to the entireanthropic-*namespace plusrequest-id/cf-ray. Saved to~/.claude/quota-status.jsonunder a newall_headerskey. Future-proofs against Anthropic adding new headers without requiring code changes. Pattern borrowed from @fgrosswig's claude-usage-dashboard proxy. cost-factormetric incost-report.mjs— Adds an overhead-ratio metric:(input + output + cache_read + cache_creation) / output. Single-number indicator of how much context is being paid per useful output token; rising values over long sessions signal cache-efficiency degradation. Surfaced in text, JSON, and Markdown output modes. Credit: @fgrosswig (methodology from claude-usage-dashboard).tools/sim-cost-reconcile.sh— One-liner wrapper aroundcost-report.mjsfor running simulation logs against the Anthropic admin API. Auto-loads the admin key from~/.config/anthropic/admin-keyorANTHROPIC_ADMIN_KEY, resolves a sim directory to its simulation.log, and passes through extra args.tools/usage-to-dashboard-ndjson.mjs— New translator tool that reads~/.claude/usage.jsonland emits NDJSON records in the schema expected by @fgrosswig's claude-usage-dashboard. Writes to~/.claude/anthropic-proxy-logs/proxy-YYYY-MM-DD.ndjson(the path his dashboard auto-discovers). Supports one-shot, follow, and stdout modes. Interceptor-specific fields (ttl_tier,ephemeral_1h_input_tokens,peak_hour, quota state) pass through his dashboard's tolerant schema unchanged. No coordination with fgrosswig required — the integration is fully one-way.- README: "Works with @fgrosswig's dashboard" section — Documents the interop pattern with a quick-setup example, explains the complementary architecture (our per-call capture + his visualization), and adds @fgrosswig to Related research and Contributors.
- docs/march-23-regression-investigation.md — Full methodology and measurements from the 2026-04-11 cross-version analysis of Claude Code v2.1.81, v2.1.83, v2.1.90, and v2.1.101. Documents the release-timing argument (regression starts mid-release-cycle → server-side change), per-version prefix sizes, per-section breakdown, per-tool drift table, and the
ScheduleWakeuptool description quote confirming the 5-minute TTL baseline from Anthropic's own product code.
The CHANGELOG was not kept in sync during this release window. The major shipped features across these versions:
- 1.6.4 —
quota-analysistool for Q5h counting investigation; test infrastructure hardening; Crunchloop DAP / @bilby91 production-validation credit. - 1.6.3 — Unit tests + CI workflow;
tengu_onyx_ploverGrowthBook flag tracking forautoDreamvisibility. - 1.6.2 — Fresh-session sort/pin fix for @bilby91's #44045 case (removed the
messages.length < 2early return); opt-in identity normalization for Agent SDKsystem[1]cache parity viaCACHE_FIX_NORMALIZE_IDENTITY=1(@labzink #44724); opt-in output-efficiency system-prompt rewrite viaCACHE_FIX_OUTPUT_EFFICIENCY_REPLACEMENT(@VictorSun92 PR). - 1.6.1 — Quota utilization (
q5h_pct,q7d_pct) logged per-call tousage.jsonlfor drain-rate analysis. - 1.6.0 — Enforce 1-hour cache TTL on accounts blocked by client-side gating. Interceptor injects
ttl: "1h"into every outgoingcache_controlblock unconditionally. - 1.5.1 — Fix MCP registration jitter cache busts (deferred-tools block sort, @bilby91 #44045).
- 1.5.0 — Add usage telemetry logging to
~/.claude/usage.jsonl;cost-report.mjsCLI tool with pricing fromrates.json, admin API cross-reference, and per-call breakdown.
For full per-commit detail on any of these releases, see git log in the repository.
- Peak hour detection — Detects Anthropic's weekday peak hours (13:00–19:00 UTC, Mon–Fri) when quota drains at an elevated rate. Writes
peak_hour: true/falsetoquota-status.jsonand logsPEAK HOURwhenCACHE_FIX_DEBUG=1. Enables status line and data analysis to separate peak vs off-peak burn rates.
- TTL tier detection — Clones the API response and drains the SSE stream to extract
ephemeral_1h_input_tokensandephemeral_5m_input_tokensfrom the usage object. Determines which cache TTL tier the server applied (1h vs 5m) and writes it to~/.claude/quota-status.jsonalongside quota data. Logs per-call cache hit rate and TTL tier whenCACHE_FIX_DEBUG=1. Useful for diagnosing stuck TTL issues (#42052). - Quota file merge — Header-based quota writes now merge with existing
quota-status.jsoninstead of replacing it, preserving the async TTL/cache data across writes.
- Prompt size measurement — When
CACHE_FIX_DEBUG=1, every API call now logs character counts for the system prompt, tool schemas, and per-type injected blocks (skills listing, MCP instructions, deferred tools, hooks). Helps users with large plugin/skill setups quantify the per-turn token cost of their configuration. - Removed prefix lock feature — The prefix lock (
CACHE_FIX_PREFIX_LOCK) has been removed. Testing revealed that the system prompt includes dynamic content (gitStatus, session-specific data) that changes on every resume, making the lock unable to match in practice. TheCACHE_FIX_PREFIX_LOCKenv var is now ignored. - Confirmed on v2.1.96 — Tested and verified against Claude Code v2.1.96.
- Removed prefix lock feature — The prefix lock (
CACHE_FIX_PREFIX_LOCK) has been removed. Testing revealed that the system prompt includes dynamic content (gitStatus, session-specific data) that changes on every resume, making the lock unable to match in practice. The feature never successfully fired in real cross-session usage. TheCACHE_FIX_PREFIX_LOCKenv var is now ignored.
- Prefix lock content hash guard — Additional safety guard hashes all non-system-reminder user content in messages[0]. Prevents prefix lock from firing if substantive context changed between sessions, even if the first 200 chars match.
New features:
- Image stripping from old tool results — Base64 images from Read tool persist in conversation history and are sent on every subsequent API call (~62,500 tokens per 500KB image per turn). Set
CACHE_FIX_IMAGE_KEEP_LAST=Nto strip images from tool results older than N user turns. Only targets tool_result images; user-pasted images are preserved. (Default: 0 = disabled) - Prefix lock for resume cache hit — Saves messages[0] content after all fixes are applied; replays it on resume to produce a byte-identical prefix and avoid a full cache rebuild. Five safety guards prevent stale or incorrect prefix replay. Set
CACHE_FIX_PREFIX_LOCK=1to enable. (Default: 0 = disabled) - GrowthBook flag dump — Logs cost/cache-relevant server-controlled flags (tengu_hawthorn_window, pewter_kestrel, slate_heron, etc.) from
~/.claude.jsonon first API call whenCACHE_FIX_DEBUG=1 - Microcompact monitoring — Detects
[Old tool result content cleared]markers in outgoing messages and logs count. Warns when total tool result chars approach the 200K budget threshold - False rate limiter detection — Logs when the client generates synthetic rate limit errors (
model: "<synthetic>") without making a real API call - Prefix snapshot diffing — Set
CACHE_FIX_PREFIXDIFF=1to capture and diff message prefix across process restarts for cache bust diagnosis
Initial release. Fixes three prompt cache bugs in Claude Code (tested through v2.1.92):
- Partial block scatter on resume — Relocates attachment blocks (skills, MCP, deferred tools, hooks) back to
messages[0]when they drift to later messages during--resume - Fingerprint instability — Stabilizes the
cc_versionfingerprint by computing it from real user text instead of meta/attachment blocks - Non-deterministic tool ordering — Sorts tool definitions alphabetically for consistent cache keys across turns