claude-code-cache-fix 1.9.0 → 1.9.2
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 +67 -0
- package/claude-fixed.bat +5 -1
- package/package.json +6 -2
- package/preload.mjs +27 -0
- package/tools/claude-vscode-wrapper.c +76 -0
- package/tools/quota-statusline.sh +24 -1
package/README.md
CHANGED
|
@@ -104,6 +104,70 @@ The wrapper dynamically resolves your npm global root, constructs a `file:///` U
|
|
|
104
104
|
|
|
105
105
|
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
106
|
|
|
107
|
+
## VS Code Extension
|
|
108
|
+
|
|
109
|
+
### Option A: VSIX extension (recommended)
|
|
110
|
+
|
|
111
|
+
The easiest path — a VS Code extension that handles everything automatically:
|
|
112
|
+
|
|
113
|
+
1. Install the interceptor: `npm install -g claude-code-cache-fix`
|
|
114
|
+
2. Download the VSIX from [GitHub Releases](https://github.com/cnighswonger/claude-code-cache-fix-vscode/releases/latest)
|
|
115
|
+
3. Install: `code --install-extension claude-code-cache-fix-0.1.0.vsix`
|
|
116
|
+
(or in VS Code: Extensions → `...` menu → "Install from VSIX...")
|
|
117
|
+
4. Restart any active Claude Code session
|
|
118
|
+
|
|
119
|
+
The extension auto-configures `claudeCode.claudeProcessWrapper` on activation. No manual settings needed. Works on Windows, macOS, and Linux.
|
|
120
|
+
|
|
121
|
+
Commands available in the VS Code command palette:
|
|
122
|
+
- **Claude Code Cache Fix: Enable** / **Disable** / **Show Status**
|
|
123
|
+
|
|
124
|
+
### Option B: Manual wrapper (if you prefer not to install the VSIX)
|
|
125
|
+
|
|
126
|
+
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.
|
|
127
|
+
|
|
128
|
+
**Linux / macOS** — create `~/bin/claude-vscode-wrapper`:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
#!/bin/bash
|
|
132
|
+
NPM_ROOT="$(npm root -g 2>/dev/null)"
|
|
133
|
+
PRELOAD="$NPM_ROOT/claude-code-cache-fix/preload.mjs"
|
|
134
|
+
shift # VS Code passes the original claude path as $1
|
|
135
|
+
export NODE_OPTIONS="--import $PRELOAD"
|
|
136
|
+
exec node "$NPM_ROOT/@anthropic-ai/claude-code/cli.js" "$@"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
chmod +x ~/bin/claude-vscode-wrapper
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Add to VS Code `settings.json`:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"claudeCode.claudeProcessWrapper": "/home/YOUR_USERNAME/bin/claude-vscode-wrapper"
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**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`):
|
|
152
|
+
|
|
153
|
+
```cmd
|
|
154
|
+
cl tools\claude-vscode-wrapper.c /Fe:claude-vscode-wrapper.exe
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Then set in VS Code `settings.json`:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"claudeCode.claudeProcessWrapper": "C:\\path\\to\\claude-vscode-wrapper.exe"
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Known limitations (VS Code)
|
|
166
|
+
|
|
167
|
+
- **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.
|
|
168
|
+
|
|
169
|
+
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)).
|
|
170
|
+
|
|
107
171
|
## How it works
|
|
108
172
|
|
|
109
173
|
The module intercepts `globalThis.fetch` before Claude Code makes API calls to `/v1/messages`. On each call it:
|
|
@@ -522,6 +586,9 @@ measurable signature of cache-efficiency degradation.
|
|
|
522
586
|
- **[@fgrosswig](https://github.com/fgrosswig)** — [claude-usage-dashboard](https://github.com/fgrosswig/claude-usage-dashboard) forensic methodology: cost-factor overhead ratio metric, `anthropic-*` header capture pattern, proxy NDJSON schema that informed our dashboard interop layer
|
|
523
587
|
- **[@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)
|
|
524
588
|
- **[@arjansingh](https://github.com/arjansingh)** — nvm-compatible wrapper script with dynamic `npm root -g` path resolution (PR #15)
|
|
589
|
+
- **[@beekamai](https://github.com/beekamai)** — Windows URL-encoding fix for `claude-fixed.bat` when npm root contains spaces (PR #17)
|
|
590
|
+
- **[@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)
|
|
591
|
+
- **[@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)
|
|
525
592
|
|
|
526
593
|
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.
|
|
527
594
|
|
package/claude-fixed.bat
CHANGED
|
@@ -17,6 +17,10 @@ REM
|
|
|
17
17
|
REM Credit: @TomTheMenace (https://github.com/anthropics/claude-code/issues/38335)
|
|
18
18
|
REM Part of claude-code-cache-fix: https://github.com/cnighswonger/claude-code-cache-fix
|
|
19
19
|
|
|
20
|
+
REM Resolve npm global root and URL-encode it so spaces (e.g. "C:\Program Files\nodejs")
|
|
21
|
+
REM don't break NODE_OPTIONS parsing. Without encoding, Node splits --import file:/// on
|
|
22
|
+
REM the literal space and fails with ERR_MODULE_NOT_FOUND: Cannot find module 'C:\Program'.
|
|
20
23
|
for /f "delims=" %%G in ('npm root -g') do set "NPM_GLOBAL=%%G"
|
|
21
|
-
|
|
24
|
+
for /f "delims=" %%U in ('powershell -NoProfile -Command "[System.Uri]::EscapeUriString(('%NPM_GLOBAL:\=/%' + '/claude-code-cache-fix/preload.mjs'))"') do set "PRELOAD_URL=%%U"
|
|
25
|
+
set NODE_OPTIONS=--import file:///%PRELOAD_URL%
|
|
22
26
|
node "%NPM_GLOBAL%\@anthropic-ai\claude-code\cli.js" %*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-cache-fix",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
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",
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
"url": "https://github.com/cnighswonger/claude-code-cache-fix/issues"
|
|
33
33
|
},
|
|
34
34
|
"homepage": "https://github.com/cnighswonger/claude-code-cache-fix#readme",
|
|
35
|
+
"funding": {
|
|
36
|
+
"type": "individual",
|
|
37
|
+
"url": "https://buymeacoffee.com/vsits"
|
|
38
|
+
},
|
|
35
39
|
"license": "MIT",
|
|
36
|
-
"author": "Chris Nighswonger"
|
|
40
|
+
"author": "Chris Nighswonger <chris@veritassuperaitsolutions.com> (https://veritassuperaitsolutions.com)"
|
|
37
41
|
}
|
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+).
|
|
@@ -1677,6 +1703,7 @@ export {
|
|
|
1677
1703
|
isHooksBlock,
|
|
1678
1704
|
isMcpBlock,
|
|
1679
1705
|
isRelocatableBlock,
|
|
1706
|
+
isClearArtifact,
|
|
1680
1707
|
rewriteOutputEfficiencyInstruction,
|
|
1681
1708
|
normalizeOutputEfficiencyReplacement,
|
|
1682
1709
|
_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
|