claude-code-cache-fix 2.0.6 → 3.0.1

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.
@@ -0,0 +1,93 @@
1
+ import https from "node:https";
2
+ import http from "node:http";
3
+ import { URL } from "node:url";
4
+ import config from "./config.mjs";
5
+
6
+ const STRIP_REQUEST_HEADERS = new Set([
7
+ "connection",
8
+ "keep-alive",
9
+ "transfer-encoding",
10
+ "te",
11
+ "upgrade",
12
+ ]);
13
+
14
+ const STRIP_RESPONSE_HEADERS = new Set([
15
+ "connection",
16
+ "keep-alive",
17
+ "transfer-encoding",
18
+ ]);
19
+
20
+ function shouldStripRequestHeader(name) {
21
+ const lower = name.toLowerCase();
22
+ return STRIP_REQUEST_HEADERS.has(lower) || lower.startsWith("proxy-");
23
+ }
24
+
25
+ function shouldStripResponseHeader(name) {
26
+ return STRIP_RESPONSE_HEADERS.has(name.toLowerCase());
27
+ }
28
+
29
+ function buildUpstreamHeaders(incomingHeaders, upstreamHostname) {
30
+ const headers = {};
31
+ for (const [key, value] of Object.entries(incomingHeaders)) {
32
+ if (shouldStripRequestHeader(key)) continue;
33
+ headers[key] = value;
34
+ }
35
+ headers["host"] = upstreamHostname;
36
+ headers["accept-encoding"] = "identity";
37
+ return headers;
38
+ }
39
+
40
+ function filterResponseHeaders(rawHeaders) {
41
+ const headers = {};
42
+ for (const [key, value] of Object.entries(rawHeaders)) {
43
+ if (shouldStripResponseHeader(key)) continue;
44
+ headers[key] = value;
45
+ }
46
+ return headers;
47
+ }
48
+
49
+ export function forwardRequest(clientReq, body, signal) {
50
+ return new Promise((resolve, reject) => {
51
+ const upstreamUrl = new URL(clientReq.url, config.upstream);
52
+
53
+ const headers = buildUpstreamHeaders(clientReq.headers, upstreamUrl.hostname);
54
+ if (body) {
55
+ headers["content-length"] = Buffer.byteLength(body).toString();
56
+ }
57
+
58
+ const isHTTPS = upstreamUrl.protocol === "https:";
59
+ const transport = isHTTPS ? https : http;
60
+ const defaultPort = isHTTPS ? 443 : 80;
61
+
62
+ const options = {
63
+ hostname: upstreamUrl.hostname,
64
+ port: upstreamUrl.port || defaultPort,
65
+ path: upstreamUrl.pathname + upstreamUrl.search,
66
+ method: clientReq.method,
67
+ headers,
68
+ timeout: config.timeout,
69
+ };
70
+
71
+ const upstreamReq = transport.request(options, (upstreamRes) => {
72
+ const responseHeaders = filterResponseHeaders(upstreamRes.headers);
73
+ resolve({ upstreamRes, responseHeaders, statusCode: upstreamRes.statusCode });
74
+ });
75
+
76
+ upstreamReq.on("error", reject);
77
+ upstreamReq.on("timeout", () => {
78
+ upstreamReq.destroy(new Error("Upstream timeout"));
79
+ });
80
+
81
+ if (signal) {
82
+ signal.addEventListener("abort", () => {
83
+ upstreamReq.destroy(new Error("Request aborted"));
84
+ }, { once: true });
85
+ }
86
+
87
+ if (body) {
88
+ upstreamReq.end(body);
89
+ } else {
90
+ upstreamReq.end();
91
+ }
92
+ });
93
+ }
@@ -0,0 +1,42 @@
1
+ import { watch } from "node:fs";
2
+ import { loadExtensions } from "./pipeline.mjs";
3
+
4
+ let debounceTimer = null;
5
+
6
+ export function startWatcher(extensionsDir, configPath, opts = {}) {
7
+ const debounceMs = opts.debounceMs ?? 100;
8
+ const onReload = opts.onReload;
9
+
10
+ function scheduleReload() {
11
+ if (debounceTimer) clearTimeout(debounceTimer);
12
+ debounceTimer = setTimeout(async () => {
13
+ try {
14
+ const exts = await loadExtensions(extensionsDir, configPath);
15
+ if (onReload) onReload(exts);
16
+ } catch (err) {
17
+ process.stderr.write(`[watcher] reload failed: ${err.message}\n`);
18
+ }
19
+ }, debounceMs);
20
+ }
21
+
22
+ const dirWatcher = watch(extensionsDir, { persistent: false }, (eventType, filename) => {
23
+ if (filename && filename.endsWith(".mjs")) {
24
+ scheduleReload();
25
+ }
26
+ });
27
+
28
+ let configWatcher = null;
29
+ try {
30
+ configWatcher = watch(configPath, { persistent: false }, () => {
31
+ scheduleReload();
32
+ });
33
+ } catch {}
34
+
35
+ return {
36
+ close() {
37
+ dirWatcher.close();
38
+ if (configWatcher) configWatcher.close();
39
+ if (debounceTimer) clearTimeout(debounceTimer);
40
+ },
41
+ };
42
+ }
@@ -79,6 +79,32 @@ was max(dualpol_lr, hail_lr) for correlation grouping." > /tmp/context.txt
79
79
 
80
80
  The user context is injected into the summarization prompt, ensuring those details appear in the output.
81
81
 
82
+ ### Pre-Clear Agent Review (Recommended)
83
+
84
+ Before `/clear`, let the agent review the summary while it still has full context. Paste this prompt into the session:
85
+
86
+ ```
87
+ I'm about to /clear this session. Read /tmp/<session-id>-compact-summary.txt — that's the summary that will be used to restore context after the clear.
88
+
89
+ Review it against your current knowledge and do the following:
90
+
91
+ 1. Write a SESSION_STATE.md in this project directory that captures anything the summary missed — especially:
92
+ - Active work state details the summary got wrong or understated
93
+ - Decisions made and their rationale that aren't in the summary
94
+ - Context about collaborators, dependencies, or constraints
95
+ - Anything you'd need to know to resume work that isn't recoverable from git
96
+
97
+ 2. Write any critical findings to memory files (if your project uses them) that should persist across sessions.
98
+
99
+ 3. Tell me what's missing from the summary so I can verify the gap is covered.
100
+
101
+ Do NOT do a /clear yourself. I will do it after you've finished writing.
102
+ ```
103
+
104
+ Replace `<session-id>` with the actual path shown in the script output.
105
+
106
+ The agent will identify gaps while it still has the context to fill them. This typically raises summary fidelity from ~85% to ~95%+.
107
+
82
108
  ### Restoring Context After /clear
83
109
 
84
110
  In the CC session:
@@ -90,7 +116,7 @@ In the CC session:
90
116
  Then as your first message:
91
117
 
92
118
  ```
93
- Read /tmp/<session-id>-compact-summary.txt for context on where we left off.
119
+ Read /tmp/<session-id>-compact-summary.txt for context on where we left off. Also read SESSION_STATE.md in this directory for additional context the summary may have missed.
94
120
  ```
95
121
 
96
122
  ## Limitations
@@ -114,7 +140,20 @@ Use the user context file to fill known gaps.
114
140
 
115
141
  ### Token cost
116
142
 
117
- The summarization call costs tokens against your Q5h quota. At ~50K extract tokens through Sonnet, expect ~1-2% Q5h per compaction. This is comparable to what `/compact` costs.
143
+ Two costs to account for:
144
+
145
+ 1. **Summarization call** — the `claude --print` call through Sonnet. At ~50K extract tokens, expect ~1-2% Q5h.
146
+ 2. **Cold start after /clear** — the first API call rebuilds the full cache from scratch. Real-world example from a 954K-token session:
147
+
148
+ ```
149
+ Before /clear: cache_read=954,399 cache_creation=0 (warm)
150
+ First call: cache_read=0 cache_creation=954,399 (cold rebuild)
151
+ Second call: cache_read=957,253 cache_creation=5,569 (warm again)
152
+ ```
153
+
154
+ The cold rebuild consumed ~15% Q5h in one call on our Max 5x account. After that single rebuild, the session is warm again and cache hits resume at 99%+.
155
+
156
+ **Total cost of a manual compact cycle:** ~17% Q5h (2% summarization + 15% cold rebuild). Compare to hitting the 1M wall and losing the session entirely.
118
157
 
119
158
  ### Requires Claude Sonnet access
120
159