claude-code-cache-fix 1.9.1 → 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 +52 -10
- 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,29 +104,69 @@ 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
|
|
107
|
+
## VS Code Extension
|
|
108
108
|
|
|
109
|
-
|
|
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`:
|
|
110
144
|
|
|
111
145
|
```json
|
|
112
146
|
{
|
|
113
|
-
"
|
|
114
|
-
"NODE_OPTIONS": "--import /path/to/claude-code-cache-fix/preload.mjs"
|
|
115
|
-
}
|
|
147
|
+
"claudeCode.claudeProcessWrapper": "/home/YOUR_USERNAME/bin/claude-vscode-wrapper"
|
|
116
148
|
}
|
|
117
149
|
```
|
|
118
150
|
|
|
119
|
-
|
|
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`:
|
|
120
158
|
|
|
121
159
|
```json
|
|
122
160
|
{
|
|
123
|
-
"claude-
|
|
124
|
-
"NODE_OPTIONS": "--import /home/username/.npm-global/lib/node_modules/claude-code-cache-fix/preload.mjs"
|
|
125
|
-
}
|
|
161
|
+
"claudeCode.claudeProcessWrapper": "C:\\path\\to\\claude-vscode-wrapper.exe"
|
|
126
162
|
}
|
|
127
163
|
```
|
|
128
164
|
|
|
129
|
-
|
|
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)).
|
|
130
170
|
|
|
131
171
|
## How it works
|
|
132
172
|
|
|
@@ -547,6 +587,8 @@ measurable signature of cache-efficiency degradation.
|
|
|
547
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)
|
|
548
588
|
- **[@arjansingh](https://github.com/arjansingh)** — nvm-compatible wrapper script with dynamic `npm root -g` path resolution (PR #15)
|
|
549
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)
|
|
550
592
|
|
|
551
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.
|
|
552
594
|
|
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
|