claude-code-cache-fix 2.0.3 → 2.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-cache-fix",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
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",
@@ -0,0 +1,138 @@
1
+ # manual-compact.sh — Manual Compaction for 1M Context Hack Sessions
2
+
3
+ ## Purpose
4
+
5
+ When using the 1M context window hack (`DISABLE_COMPACT=1` + `CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000`), the `/compact` command is disabled by CC. This tool provides a manual compaction alternative: extract the conversation, summarize it via Claude, and restore context after `/clear`.
6
+
7
+ **This tool is specifically for sessions running the 1M hack.** If you have `/compact` available, use that instead — it's built-in, integrated, and handles the full compaction lifecycle automatically.
8
+
9
+ ## How It Works
10
+
11
+ 1. Extracts conversation turns from the session JSONL transcript
12
+ 2. Splits turns into three weighted segments:
13
+ - **Foundational** (first 20%) — truncated to 200 chars each
14
+ - **Working** (middle 40%) — truncated to 400 chars each
15
+ - **Active** (last 40%) — preserved up to 2000 chars each
16
+ 3. Sends the weighted extract to Claude Sonnet for summarization
17
+ 4. Produces a structured summary optimized for agent handoff
18
+
19
+ The weighting ensures recent active work (the part you're most likely to need) gets full detail, while earlier completed work is compressed.
20
+
21
+ ## Usage
22
+
23
+ ```bash
24
+ # By project directory (recommended) — auto-finds the most recent session
25
+ manual-compact.sh ~/git_repos/myproject
26
+
27
+ # By project directory with user context
28
+ manual-compact.sh ~/git_repos/myproject /tmp/context.txt
29
+
30
+ # By direct JSONL path (if you know the exact session)
31
+ manual-compact.sh ~/.claude/projects/-home-user-git-repos-myproject/abc123.jsonl
32
+
33
+ # By direct JSONL path with user context
34
+ manual-compact.sh ~/.claude/projects/-home-user-git-repos-myproject/abc123.jsonl /tmp/context.txt
35
+ ```
36
+
37
+ When you pass a project directory, the tool:
38
+ 1. Converts it to CC's internal project path format
39
+ 2. Finds the most recently modified session JSONL
40
+ 3. Shows you the session details (modified date, size)
41
+ 4. **Asks for confirmation** before proceeding
42
+
43
+ ### WARNING: Wrong Session = Wrong Context
44
+
45
+ **If you select the wrong session JSONL, the summary will be from a completely different conversation.** Loading that summary after `/clear` will inject false context — the agent will confidently act on information from another session, another project, or another agent's work.
46
+
47
+ Always:
48
+ - Verify the session timestamp matches your active session
49
+ - Review the summary output before feeding it to an agent
50
+ - When in doubt, check the last few lines of the JSONL to confirm it's the right conversation
51
+
52
+ ### Example: Basic Compaction
53
+
54
+ ```bash
55
+ ./tools/manual-compact.sh ~/git_repos/kanfei-nowcast-e3b
56
+ ```
57
+
58
+ ```
59
+ Project directory: /home/manager/git_repos/kanfei_nowcast_e3b
60
+ Auto-detected session: db11f377-4ca8-4fc3-9b6d-1069da58c1b2.jsonl
61
+ Modified: 2026-04-19 13:26:42
62
+ Size: 4.8M
63
+
64
+ Is this the correct session? [Y/n]
65
+ ```
66
+
67
+ Output: `/tmp/db11f377-...-compact-summary.txt`
68
+
69
+ ### Example: With User Context
70
+
71
+ If there's specific context you know the summary might miss:
72
+
73
+ ```bash
74
+ echo "The MR2 OOM debugging took 3 days. The PR #75 architectural recommendation
75
+ was max(dualpol_lr, hail_lr) for correlation grouping." > /tmp/context.txt
76
+
77
+ ./tools/manual-compact.sh ~/git_repos/kanfei-nowcast-e3b /tmp/context.txt
78
+ ```
79
+
80
+ The user context is injected into the summarization prompt, ensuring those details appear in the output.
81
+
82
+ ### Restoring Context After /clear
83
+
84
+ In the CC session:
85
+
86
+ ```
87
+ /clear
88
+ ```
89
+
90
+ Then as your first message:
91
+
92
+ ```
93
+ Read /tmp/<session-id>-compact-summary.txt for context on where we left off.
94
+ ```
95
+
96
+ ## Limitations
97
+
98
+ ### This tool is a workaround, not a replacement for /compact
99
+
100
+ - `/compact` operates inside CC with full access to the internal message array, system prompt, tool schemas, and session state. This tool only sees the JSONL transcript, which is a subset.
101
+ - `/compact` preserves CC's internal state (tool registration, MCP connections, plugin state). This tool + `/clear` resets all of that. The agent must re-establish any stateful connections.
102
+ - `/compact` is atomic — one command, seamless continuation. This tool requires `/clear` + paste, which is a hard context boundary.
103
+
104
+ ### Summary fidelity
105
+
106
+ Tested at ~95% fidelity for active work resumption, ~70% for broader project context. Gaps typically include:
107
+
108
+ - **Operational debugging history** — multi-day debugging sagas compress away
109
+ - **Timeline information** — the summary doesn't indicate when things happened or how long they took
110
+ - **Depth of architectural discussions** — detailed technical recommendations get compressed to one-liners
111
+ - **Background process context** — overnight watchers, cron monitoring, polling patterns
112
+
113
+ Use the user context file to fill known gaps.
114
+
115
+ ### Token cost
116
+
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.
118
+
119
+ ### Requires Claude Sonnet access
120
+
121
+ The tool uses `claude --print --model claude-sonnet-4-6` for summarization. Sonnet is used instead of Opus to minimize Q5h impact. If Sonnet is unavailable, change the model in the script.
122
+
123
+ ## Why the 1M Hack Disables /compact
124
+
125
+ The 1M context hack works by setting `DISABLE_COMPACT=1`, which CC reads as "disable all compaction." CC's code uses a single env var to control both:
126
+ - The context window calculation (`ff()` returns 1M when `DISABLE_COMPACT=1`)
127
+ - The `/compact` command availability (`isEnabled: () => !DISABLE_COMPACT`)
128
+
129
+ These are coupled in CC's source — there is no way to get 1M context AND `/compact` simultaneously without CC code changes. The coupling is in the CC binary, not in our interceptor.
130
+
131
+ We attempted to toggle `DISABLE_COMPACT` via the interceptor (set during API calls, unset between turns), but CC registers available commands at startup before any API call, so the toggle cannot re-enable `/compact` after session start.
132
+
133
+ ## Requirements
134
+
135
+ - Claude Code v2.1.112 (the last Node.js version — v2.1.113+ uses Bun)
136
+ - The cache-fix interceptor loaded via `NODE_OPTIONS=--import`
137
+ - `DISABLE_COMPACT=1` and `CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000` set
138
+ - `claude` CLI available in PATH (used for summarization)
@@ -0,0 +1,212 @@
1
+ #!/bin/bash
2
+ # manual-compact.sh — Generate a compaction summary for a CC session
3
+ #
4
+ # Usage:
5
+ # manual-compact.sh <project-dir-or-session-jsonl> [user-context-file]
6
+ #
7
+ # Accepts either:
8
+ # - A project working directory (e.g. ~/git_repos/myproject)
9
+ # → auto-finds the most recent session JSONL
10
+ # - A direct path to a session JSONL file
11
+ #
12
+ # Produces a summary at /tmp/<session-id>-compact-summary.txt
13
+ # that can be pasted or referenced after /clear.
14
+ #
15
+ # The optional user-context-file is additional context the user wants
16
+ # preserved in the summary (equivalent to /compact <instructions>).
17
+ #
18
+ # WARNING: Using the wrong session JSONL will produce a summary from
19
+ # a DIFFERENT conversation. Loading that into your session after /clear
20
+ # will inject completely wrong context. Always verify the output before
21
+ # feeding it to an agent.
22
+
23
+ set -euo pipefail
24
+
25
+ if [ $# -lt 1 ]; then
26
+ echo "Usage: $0 <project-dir-or-session-jsonl> [user-context-file]"
27
+ echo ""
28
+ echo "Generates a compaction summary from a CC session JSONL transcript."
29
+ echo "After /clear, reference the output file to restore context."
30
+ echo ""
31
+ echo "Arguments:"
32
+ echo " <project-dir> Working directory of the CC session (e.g. ~/git_repos/myproject)"
33
+ echo " Auto-detects the most recent session JSONL."
34
+ echo " <session-jsonl> Direct path to a session JSONL file."
35
+ echo " [user-context] Optional file with additional context to preserve."
36
+ echo ""
37
+ echo "WARNING: Verify the output summary before loading it after /clear."
38
+ echo " A wrong session JSONL = wrong context = confused agent."
39
+ exit 1
40
+ fi
41
+
42
+ INPUT="$1"
43
+ USER_CONTEXT_FILE="${2:-}"
44
+
45
+ # Determine if input is a directory or a JSONL file
46
+ if [ -d "$INPUT" ]; then
47
+ # Convert project directory to CC's project path format
48
+ REAL_PATH=$(realpath "$INPUT")
49
+ # CC replaces / with - and prepends -
50
+ PROJECT_KEY=$(echo "$REAL_PATH" | sed 's|/|-|g')
51
+ PROJECT_DIR="$HOME/.claude/projects/${PROJECT_KEY}"
52
+
53
+ if [ ! -d "$PROJECT_DIR" ]; then
54
+ echo "ERROR: No CC project found for directory: $INPUT"
55
+ echo " Expected: $PROJECT_DIR"
56
+ echo ""
57
+ echo "Available projects:"
58
+ ls -d ~/.claude/projects/*/ 2>/dev/null | head -10
59
+ exit 1
60
+ fi
61
+
62
+ # Find the most recent JSONL (exclude subdirectories like subagents/)
63
+ JSONL=$(find "$PROJECT_DIR" -maxdepth 1 -name "*.jsonl" -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)
64
+
65
+ if [ -z "$JSONL" ]; then
66
+ echo "ERROR: No session JSONL found in: $PROJECT_DIR"
67
+ exit 1
68
+ fi
69
+
70
+ echo "Project directory: $INPUT"
71
+ echo "Auto-detected session: $(basename "$JSONL")"
72
+ echo " Modified: $(stat -c '%y' "$JSONL" | cut -d'.' -f1)"
73
+ echo " Size: $(du -h "$JSONL" | cut -f1)"
74
+ echo ""
75
+ read -p "Is this the correct session? [Y/n] " CONFIRM
76
+ if [[ "${CONFIRM:-Y}" =~ ^[Nn] ]]; then
77
+ echo ""
78
+ echo "Available sessions in $PROJECT_DIR:"
79
+ ls -lt "$PROJECT_DIR"/*.jsonl 2>/dev/null | awk '{print " " $6, $7, $8, $NF}'
80
+ echo ""
81
+ echo "Re-run with the specific JSONL path."
82
+ exit 1
83
+ fi
84
+ elif [ -f "$INPUT" ]; then
85
+ JSONL="$INPUT"
86
+ else
87
+ echo "ERROR: $INPUT is not a directory or file."
88
+ exit 1
89
+ fi
90
+
91
+ SESSION_ID=$(basename "$JSONL" .jsonl)
92
+ OUTPUT="/tmp/${SESSION_ID}-compact-summary.txt"
93
+ EXTRACT="/tmp/${SESSION_ID}-conv-extract.txt"
94
+
95
+ echo ""
96
+ echo "Extracting conversation from: $JSONL"
97
+
98
+ # Extract conversation turns, keeping more detail for recent turns
99
+ python3 << PYEOF
100
+ import json, sys
101
+
102
+ conversation = []
103
+ with open("$JSONL") as f:
104
+ for line in f:
105
+ try:
106
+ d = json.loads(line.strip())
107
+ if d.get('type') == 'user':
108
+ msg = d.get('message', {})
109
+ content = msg.get('content', '')
110
+ if isinstance(content, str) and len(content.strip()) > 0:
111
+ if content.startswith('<local-command') or content.startswith('<command-name>'):
112
+ continue
113
+ conversation.append(('user', content))
114
+ elif isinstance(content, list):
115
+ texts = []
116
+ for b in content:
117
+ if isinstance(b, dict):
118
+ if b.get('type') == 'text' and b.get('text'):
119
+ t = b['text']
120
+ if not t.startswith('<local-command') and not t.startswith('<command-name>'):
121
+ texts.append(t)
122
+ elif b.get('type') == 'tool_result' and b.get('content'):
123
+ c = b['content']
124
+ if isinstance(c, str):
125
+ texts.append(c)
126
+ elif isinstance(c, list):
127
+ for tb in c:
128
+ if isinstance(tb, dict) and tb.get('text'):
129
+ texts.append(tb['text'])
130
+ if texts:
131
+ conversation.append(('user', ' '.join(texts)))
132
+ elif d.get('type') == 'assistant':
133
+ msg = d.get('message', {})
134
+ content = msg.get('content', [])
135
+ if isinstance(content, list):
136
+ texts = [b.get('text', '') for b in content if isinstance(b, dict) and b.get('type') == 'text' and b.get('text')]
137
+ if texts:
138
+ conversation.append(('assistant', ' '.join(texts)))
139
+ except:
140
+ pass
141
+
142
+ total = len(conversation)
143
+ if total == 0:
144
+ print("No conversation found.", file=sys.stderr)
145
+ sys.exit(1)
146
+
147
+ # Split into three segments with different detail levels:
148
+ # - First 20%: truncate to 200 chars each (foundational context)
149
+ # - Middle 40%: truncate to 400 chars each (working context)
150
+ # - Last 40%: full text up to 2000 chars each (active work — most important)
151
+ seg1_end = int(total * 0.2)
152
+ seg2_end = int(total * 0.6)
153
+
154
+ with open("$EXTRACT", 'w') as f:
155
+ f.write("=== FOUNDATIONAL CONTEXT (early session) ===\n\n")
156
+ for role, text in conversation[:seg1_end]:
157
+ f.write(f"[{role}]: {text[:200]}\n\n")
158
+
159
+ f.write("\n=== WORKING CONTEXT (mid session) ===\n\n")
160
+ for role, text in conversation[seg1_end:seg2_end]:
161
+ f.write(f"[{role}]: {text[:400]}\n\n")
162
+
163
+ f.write("\n=== ACTIVE WORK (recent — preserve in full detail) ===\n\n")
164
+ for role, text in conversation[seg2_end:]:
165
+ f.write(f"[{role}]: {text[:2000]}\n\n")
166
+
167
+ import os
168
+ size = os.path.getsize("$EXTRACT")
169
+ print(f"Extracted {total} turns ({size:,} bytes, ~{size//4:,} est. tokens)")
170
+ print(f" Foundational: {seg1_end} turns (truncated to 200 chars)")
171
+ print(f" Working: {seg2_end - seg1_end} turns (truncated to 400 chars)")
172
+ print(f" Active: {total - seg2_end} turns (up to 2000 chars)")
173
+ PYEOF
174
+
175
+ # Build the summarization prompt
176
+ USER_CONTEXT=""
177
+ if [ -n "$USER_CONTEXT_FILE" ] && [ -f "$USER_CONTEXT_FILE" ]; then
178
+ USER_CONTEXT=$(cat "$USER_CONTEXT_FILE")
179
+ echo "User context loaded from: $USER_CONTEXT_FILE"
180
+ fi
181
+
182
+ PROMPT="Summarize this conversation for context continuity after a /clear.
183
+
184
+ CRITICAL PRIORITIES (in order):
185
+ 1. ACTIVE WORK STATE — What is the agent doing RIGHT NOW? What branch, what uncommitted changes, what task is in progress, what was the last action taken? This is the most important section. Be precise about exactly where things stand — do not understate progress.
186
+ 2. RECENT DECISIONS — Key decisions made in the last ~20% of the conversation and their rationale.
187
+ 3. PENDING NEXT STEPS — What was about to happen next? What was queued?
188
+ 4. COMPLETED WORK — PRs merged, issues closed, features shipped. Brief — the git history has the details.
189
+ 5. FOUNDATIONAL CONTEXT — Agent identity, repo location, key collaborators, infrastructure. Brief.
190
+
191
+ FORMAT: Use headers and bullet points. Be specific about file paths, branch names, commit SHAs, function names. The agent reading this will have zero prior context — every detail that matters must be explicit.
192
+
193
+ DO NOT understate progress on in-flight work. If the last 20% of the conversation shows implementation was done, say it was done — do not say 'investigation started'."
194
+
195
+ if [ -n "$USER_CONTEXT" ]; then
196
+ PROMPT="$PROMPT
197
+
198
+ ADDITIONAL USER CONTEXT TO PRESERVE:
199
+ $USER_CONTEXT"
200
+ fi
201
+
202
+ echo ""
203
+ echo "Sending to Claude for summarization..."
204
+
205
+ cat "$EXTRACT" | claude --print --model claude-sonnet-4-6 "$PROMPT" > "$OUTPUT" 2>/dev/null
206
+
207
+ SIZE=$(wc -c < "$OUTPUT")
208
+ echo ""
209
+ echo "Summary generated: $OUTPUT ($SIZE bytes)"
210
+ echo ""
211
+ echo "To restore context after /clear:"
212
+ echo " Read $OUTPUT for context on where we left off."