claude-code-cache-fix 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,19 @@
2
2
 
3
3
  English | [中文](./README.zh.md) | [Português](./docs/guia-pt-br.md)
4
4
 
5
- Fixes prompt cache regressions in [Claude Code](https://github.com/anthropics/claude-code) that cause **up to 20x cost increase** on resumed sessions, plus monitoring for silent context degradation. Confirmed through v2.1.97.
5
+ Fixes prompt cache regressions in [Claude Code](https://github.com/anthropics/claude-code) that cause **up to 20x cost increase** on resumed sessions, plus monitoring for silent context degradation. Confirmed through v2.1.107.
6
+
7
+ ## Security model
8
+
9
+ > **This interceptor patches `globalThis.fetch`.** By design, it has full read/write access to all API requests and responses in the Claude Code process. This is inherent to the approach — any fetch interceptor, proxy, or gateway has this position.
10
+
11
+ **What it does:** Modifies outgoing request structure (block order, fingerprint, TTL, git-status) to fix cache bugs. Reads response headers and SSE usage data for monitoring.
12
+
13
+ **What it does NOT do:** No network calls from the interceptor. All telemetry is written to local files under `~/.claude/`. No data leaves your machine unless you explicitly opt in to [claude-code-meter](https://github.com/cnighswonger/claude-code-meter) sharing (separate package, requires interactive consent).
14
+
15
+ **Supply chain:** Single unminified file (`preload.mjs`, ~1,700 lines). One dependency (`zod` for schema validation in tests only). Review before installing. npm provenance links each published version to its source commit.
16
+
17
+ **Independent audit:** [Assessed as "LEGITIMATE TOOL"](https://github.com/anthropics/claude-code/issues/38335#issuecomment-4244413605) by @TheAuditorTool (2026-04-14).
6
18
 
7
19
  ## The problem
8
20
 
@@ -104,29 +116,69 @@ The wrapper dynamically resolves your npm global root, constructs a `file:///` U
104
116
 
105
117
  Credit: [@TomTheMenace](https://github.com/anthropics/claude-code/issues/38335) contributed the Windows wrapper and validated the interceptor across a 7.5-hour, 536-call Opus 4.6 session on Windows — 98.4% cache hit rate, 81% of calls had fingerprint instability that the interceptor corrected.
106
118
 
107
- ## VS Code Extension (experimental)
119
+ ## VS Code Extension
120
+
121
+ ### Option A: VSIX extension (recommended)
122
+
123
+ The easiest path — a VS Code extension that handles everything automatically:
124
+
125
+ 1. Install the interceptor: `npm install -g claude-code-cache-fix`
126
+ 2. Download the VSIX from [GitHub Releases](https://github.com/cnighswonger/claude-code-cache-fix-vscode/releases/latest)
127
+ 3. Install: `code --install-extension claude-code-cache-fix-0.1.0.vsix`
128
+ (or in VS Code: Extensions → `...` menu → "Install from VSIX...")
129
+ 4. Restart any active Claude Code session
130
+
131
+ The extension auto-configures `claudeCode.claudeProcessWrapper` on activation. No manual settings needed. Works on Windows, macOS, and Linux.
132
+
133
+ Commands available in the VS Code command palette:
134
+ - **Claude Code Cache Fix: Enable** / **Disable** / **Show Status**
135
+
136
+ ### Option B: Manual wrapper (if you prefer not to install the VSIX)
137
+
138
+ The VS Code Claude Code extension spawns `claude.exe` / `claude` as a subprocess. The `claude-code.environmentVariables` setting does **not** propagate `NODE_OPTIONS`, so a process wrapper is required.
139
+
140
+ **Linux / macOS** — create `~/bin/claude-vscode-wrapper`:
108
141
 
109
- If you use Claude Code through the VS Code extension rather than the CLI, you may be able to load the interceptor via VS Code settings:
142
+ ```bash
143
+ #!/bin/bash
144
+ NPM_ROOT="$(npm root -g 2>/dev/null)"
145
+ PRELOAD="$NPM_ROOT/claude-code-cache-fix/preload.mjs"
146
+ shift # VS Code passes the original claude path as $1
147
+ export NODE_OPTIONS="--import $PRELOAD"
148
+ exec node "$NPM_ROOT/@anthropic-ai/claude-code/cli.js" "$@"
149
+ ```
150
+
151
+ ```bash
152
+ chmod +x ~/bin/claude-vscode-wrapper
153
+ ```
154
+
155
+ Add to VS Code `settings.json`:
110
156
 
111
157
  ```json
112
158
  {
113
- "claude-code.environmentVariables": {
114
- "NODE_OPTIONS": "--import /path/to/claude-code-cache-fix/preload.mjs"
115
- }
159
+ "claudeCode.claudeProcessWrapper": "/home/YOUR_USERNAME/bin/claude-vscode-wrapper"
116
160
  }
117
161
  ```
118
162
 
119
- Replace `/path/to` with your npm global root (`npm root -g`). Example for a typical Linux setup:
163
+ **Windows** — `.bat`/`.cmd` wrappers fail because the extension uses `child_process.spawn()` without `shell: true`. Use the C wrapper source included in this package (`tools/claude-vscode-wrapper.c`):
164
+
165
+ ```cmd
166
+ cl tools\claude-vscode-wrapper.c /Fe:claude-vscode-wrapper.exe
167
+ ```
168
+
169
+ Then set in VS Code `settings.json`:
120
170
 
121
171
  ```json
122
172
  {
123
- "claude-code.environmentVariables": {
124
- "NODE_OPTIONS": "--import /home/username/.npm-global/lib/node_modules/claude-code-cache-fix/preload.mjs"
125
- }
173
+ "claudeCode.claudeProcessWrapper": "C:\\path\\to\\claude-vscode-wrapper.exe"
126
174
  }
127
175
  ```
128
176
 
129
- **Status: needs community testing.** We've confirmed the `claude-code.environmentVariables` setting exists but haven't verified it propagates `NODE_OPTIONS` to the CC subprocess. If you test this, please report back on [#16](https://github.com/cnighswonger/claude-code-cache-fix/issues/16).
177
+ ### Known limitations (VS Code)
178
+
179
+ - **Fingerprint fix auto-disables**: The VS Code extension constructs `messages[0]` differently than the CLI, causing the fingerprint safety check to trip. Use `CACHE_FIX_SKIP_FINGERPRINT=1` as a workaround. All other fixes (relocate, tool sort, TTL, /clear artifact strip) work normally.
180
+
181
+ Credit: [@JEONG-JIWOO](https://github.com/JEONG-JIWOO) and [@X-15](https://github.com/X-15) for the VS Code extension investigation and C wrapper ([#16](https://github.com/cnighswonger/claude-code-cache-fix/issues/16)).
130
182
 
131
183
  ## How it works
132
184
 
@@ -226,6 +278,7 @@ Add to `~/.claude/settings.json`:
226
278
  ```json
227
279
  {
228
280
  "statusLine": {
281
+ "type": "command",
229
282
  "command": "~/.claude/hooks/quota-statusline.sh"
230
283
  }
231
284
  }
@@ -547,6 +600,8 @@ measurable signature of cache-efficiency degradation.
547
600
  - **[@TomTheMenace](https://github.com/TomTheMenace)** — Windows `.bat` wrapper for the interceptor, first Windows platform validation (7.5h/536-call Opus 4.6 session, 98.4% cache hit rate, 81% fingerprint instability corrected)
548
601
  - **[@arjansingh](https://github.com/arjansingh)** — nvm-compatible wrapper script with dynamic `npm root -g` path resolution (PR #15)
549
602
  - **[@beekamai](https://github.com/beekamai)** — Windows URL-encoding fix for `claude-fixed.bat` when npm root contains spaces (PR #17)
603
+ - **[@JEONG-JIWOO](https://github.com/JEONG-JIWOO)** — VS Code extension investigation: discovered `claudeCode.claudeProcessWrapper` as the working integration path, wrote the C wrapper for Windows (#16)
604
+ - **[@X-15](https://github.com/X-15)** — VS Code extension validation, per-fix health status analysis confirming safety check behavior on v2.1.105 (#16)
550
605
 
551
606
  If you contributed to the community effort on these issues and aren't listed here, please open an issue or PR — we want to credit everyone properly.
552
607
 
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "claude-code-cache-fix",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "Fixes prompt cache regression in Claude Code that causes up to 20x cost increase on resumed sessions",
5
5
  "type": "module",
6
6
  "exports": "./preload.mjs",
7
7
  "main": "./preload.mjs",
8
8
  "files": [
9
9
  "preload.mjs",
10
+ "postinstall.js",
10
11
  "tools/",
11
12
  "claude-fixed.bat"
12
13
  ],
@@ -14,7 +15,8 @@
14
15
  "node": ">=18"
15
16
  },
16
17
  "scripts": {
17
- "test": "node --test"
18
+ "test": "node --test",
19
+ "postinstall": "node postinstall.js"
18
20
  },
19
21
  "keywords": [
20
22
  "claude-code",
@@ -32,6 +34,10 @@
32
34
  "url": "https://github.com/cnighswonger/claude-code-cache-fix/issues"
33
35
  },
34
36
  "homepage": "https://github.com/cnighswonger/claude-code-cache-fix#readme",
37
+ "funding": {
38
+ "type": "individual",
39
+ "url": "https://buymeacoffee.com/vsits"
40
+ },
35
41
  "license": "MIT",
36
- "author": "Chris Nighswonger"
42
+ "author": "Chris Nighswonger <chris@veritassuperaitsolutions.com> (https://veritassuperaitsolutions.com)"
37
43
  }
package/postinstall.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ console.log(`
3
+ ┌─────────────────────────────────────────────────────────────────────┐
4
+ │ claude-code-cache-fix — SECURITY NOTICE │
5
+ │ │
6
+ │ This interceptor patches globalThis.fetch to fix prompt cache │
7
+ │ bugs in Claude Code. By design, it has full read/write access │
8
+ │ to all API requests and responses in the Claude Code process. │
9
+ │ │
10
+ │ • All telemetry is LOCAL ONLY (no network calls from interceptor) │
11
+ │ • Source is a single unminified file: preload.mjs (~1,700 lines) │
12
+ │ • Review before use: github.com/cnighswonger/claude-code-cache-fix │
13
+ │ │
14
+ │ Independent audit: github.com/anthropics/claude-code/issues/38335 │
15
+ │ (search "TheAuditorTool" — assessed as LEGITIMATE TOOL) │
16
+ └─────────────────────────────────────────────────────────────────────┘
17
+ `);
package/preload.mjs CHANGED
@@ -189,6 +189,20 @@ function isRelocatableBlock(text) {
189
189
  isMcpBlock(text)
190
190
  );
191
191
  }
192
+ /**
193
+ * Detect /clear command artifacts that bleed into the next session's messages[0].
194
+ * These blocks break prefix cache because a post-/clear session has different
195
+ * messages[0] content than a truly fresh session.
196
+ * Bug: anthropics/claude-code#47756
197
+ */
198
+ function isClearArtifact(text) {
199
+ if (typeof text !== "string") return false;
200
+ return (
201
+ text.startsWith("<local-command-caveat>") ||
202
+ text.startsWith("<command-name>") ||
203
+ text.startsWith("<local-command-stdout>")
204
+ );
205
+ }
192
206
 
193
207
  /**
194
208
  * Sort skill listing entries for deterministic ordering (prevents cache bust
@@ -332,6 +346,18 @@ function normalizeResumeMessages(messages) {
332
346
  const firstMsg = messages[firstUserIdx];
333
347
  if (!Array.isArray(firstMsg?.content)) return messages;
334
348
 
349
+ // FIX: Strip /clear command artifacts from messages[0] (anthropics/claude-code#47756).
350
+ // After /clear, CC leaves <local-command-caveat>, <command-name>/clear, and
351
+ // <local-command-stdout> blocks in messages[0] of the new session, breaking
352
+ // prefix match vs a truly fresh session.
353
+ const beforeClearStrip = firstMsg.content.length;
354
+ firstMsg.content = firstMsg.content.filter((block) => !isClearArtifact(block.text || ""));
355
+ if (firstMsg.content.length < beforeClearStrip) {
356
+ const stripped = beforeClearStrip - firstMsg.content.length;
357
+ debugLog(`APPLIED: stripped ${stripped} /clear artifact block(s) from messages[0]`);
358
+ recordFixResult("relocate", "applied");
359
+ }
360
+
335
361
  // FIX: Check if ANY relocatable blocks are scattered outside first user msg.
336
362
  // The old check (firstAlreadyHas → skip) missed partial scatter where some
337
363
  // blocks stay in messages[0] but others drift to later messages (v2.1.89+).
@@ -844,6 +870,7 @@ function printHealthLine() {
844
870
  if (FIXES_DISABLED) {
845
871
  debugLog("HEALTH: all fixes disabled via CACHE_FIX_DISABLED=1 (monitoring active)");
846
872
  }
873
+ debugLog("SECURITY: This interceptor has full read/write access to API requests. All telemetry is local only — no network calls. Source: github.com/cnighswonger/claude-code-cache-fix");
847
874
  }
848
875
 
849
876
  // --------------------------------------------------------------------------
@@ -1677,6 +1704,7 @@ export {
1677
1704
  isHooksBlock,
1678
1705
  isMcpBlock,
1679
1706
  isRelocatableBlock,
1707
+ isClearArtifact,
1680
1708
  rewriteOutputEfficiencyInstruction,
1681
1709
  normalizeOutputEfficiencyReplacement,
1682
1710
  _pinnedBlocks, // exported so tests can reset between runs
@@ -0,0 +1,76 @@
1
+ /*
2
+ * claude-vscode-wrapper.c — Native wrapper for Claude Code + cache-fix on VS Code (Windows)
3
+ *
4
+ * The VS Code Claude extension uses child_process.spawn() without shell: true,
5
+ * so .bat/.cmd wrappers fail with EINVAL. This native exe sets NODE_OPTIONS
6
+ * and spawns node cli.js with the interceptor loaded.
7
+ *
8
+ * Compile: cl claude-vscode-wrapper.c /Fe:claude-vscode-wrapper.exe
9
+ * or: gcc -o claude-vscode-wrapper.exe claude-vscode-wrapper.c
10
+ *
11
+ * Usage in VS Code settings.json:
12
+ * { "claudeCode.claudeProcessWrapper": "C:\\path\\to\\claude-vscode-wrapper.exe" }
13
+ *
14
+ * Credit: @JEONG-JIWOO (original implementation, #16)
15
+ */
16
+
17
+ #include <stdio.h>
18
+ #include <stdlib.h>
19
+ #include <string.h>
20
+ #include <process.h>
21
+ #include <windows.h>
22
+
23
+ int main(int argc, char *argv[]) {
24
+ char *appdata = getenv("APPDATA");
25
+ if (!appdata) {
26
+ fprintf(stderr, "APPDATA not set\n");
27
+ return 1;
28
+ }
29
+
30
+ /* Build the preload path with forward slashes for file:// URL */
31
+ char preload[MAX_PATH];
32
+ snprintf(preload, sizeof(preload),
33
+ "%s\\npm\\node_modules\\claude-code-cache-fix\\preload.mjs", appdata);
34
+
35
+ char preload_url[MAX_PATH];
36
+ strcpy(preload_url, preload);
37
+ for (char *p = preload_url; *p; p++) {
38
+ if (*p == '\\') *p = '/';
39
+ }
40
+
41
+ /* URL-encode spaces for NODE_OPTIONS parsing */
42
+ char encoded_url[MAX_PATH * 3];
43
+ char *dst = encoded_url;
44
+ for (const char *src = preload_url; *src && dst < encoded_url + sizeof(encoded_url) - 4; src++) {
45
+ if (*src == ' ') {
46
+ *dst++ = '%'; *dst++ = '2'; *dst++ = '0';
47
+ } else {
48
+ *dst++ = *src;
49
+ }
50
+ }
51
+ *dst = '\0';
52
+
53
+ char node_opts[MAX_PATH * 4];
54
+ snprintf(node_opts, sizeof(node_opts),
55
+ "NODE_OPTIONS=--import file:///%s", encoded_url);
56
+ _putenv(node_opts);
57
+
58
+ /* Path to Claude Code CLI */
59
+ char cli_path[MAX_PATH];
60
+ snprintf(cli_path, sizeof(cli_path),
61
+ "%s\\npm\\node_modules\\@anthropic-ai\\claude-code\\cli.js", appdata);
62
+
63
+ /* Build argv: skip argv[1] (original claude path passed by extension) */
64
+ char **new_argv = malloc(sizeof(char *) * (argc + 2));
65
+ if (!new_argv) return 1;
66
+
67
+ new_argv[0] = "node";
68
+ new_argv[1] = cli_path;
69
+ int j = 2;
70
+ for (int i = 2; i < argc; i++) {
71
+ new_argv[j++] = argv[i];
72
+ }
73
+ new_argv[j] = NULL;
74
+
75
+ return _spawnvp(_P_WAIT, "node", (const char *const *)new_argv);
76
+ }
@@ -6,9 +6,33 @@
6
6
  input=$(cat)
7
7
 
8
8
  JSONL="$HOME/.claude/claude-meter.jsonl"
9
+ QS="$HOME/.claude/quota-status.json"
9
10
 
11
+ # Primary source: claude-meter.jsonl (requires claude-code-meter package)
12
+ # Fallback: quota-status.json (written by claude-code-cache-fix interceptor)
10
13
  if [ -f "$JSONL" ]; then
11
14
  last=$(tail -1 "$JSONL" 2>/dev/null)
15
+ elif [ -f "$QS" ]; then
16
+ # Translate quota-status.json into the same shape the Python expects
17
+ last=$(python3 -c "
18
+ import json, pathlib
19
+ qs = json.load(open(pathlib.Path.home() / '.claude' / 'quota-status.json'))
20
+ fh = qs.get('five_hour', {})
21
+ sd = qs.get('seven_day', {})
22
+ print(json.dumps({
23
+ 'q5h': fh.get('utilization', 0),
24
+ 'q7d': sd.get('utilization', 0),
25
+ 'q5h_reset': fh.get('resets_at', 0),
26
+ 'q7d_reset': sd.get('resets_at', 0),
27
+ 'qoverage': qs.get('overage_status', ''),
28
+ 'ts': qs.get('timestamp', ''),
29
+ }))
30
+ " 2>/dev/null)
31
+ else
32
+ exit 0
33
+ fi
34
+
35
+ if [ -z "$last" ]; then exit 0; fi
12
36
 
13
37
  result=$(echo "$last" | python3 -c "
14
38
  import sys, json
@@ -83,4 +107,3 @@ print(label)
83
107
  " 2>/dev/null)
84
108
 
85
109
  [ -n "$result" ] && echo "$result"
86
- fi