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 +66 -11
- package/package.json +9 -3
- package/postinstall.js +17 -0
- package/preload.mjs +28 -0
- package/tools/claude-vscode-wrapper.c +76 -0
- package/tools/quota-statusline.sh +24 -1
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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.
|
|
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
|