claude-code-cache-fix 2.0.4 → 2.0.6

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.4",
3
+ "version": "2.0.6",
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",
package/preload.mjs CHANGED
@@ -590,7 +590,17 @@ function isBookkeepingReminder(text) {
590
590
  // correctly.
591
591
  // --------------------------------------------------------------------------
592
592
 
593
- const CACHE_CONTROL_CANONICAL_MARKER = { type: "ephemeral", ttl: "1h" };
593
+ // Detected per-request from existing markers. Default 1h; downgraded to 5m
594
+ // if any existing block already carries ttl="5m" (Q5h=100% tier).
595
+ // The API rejects 1h markers after 5m markers, so all injected markers
596
+ // must match the lowest existing tier.
597
+ let _detectedTtlTier = "1h";
598
+
599
+ function getCanonicalMarker() {
600
+ return { type: "ephemeral", ttl: _detectedTtlTier };
601
+ }
602
+
603
+ const CACHE_CONTROL_CANONICAL_MARKER_LEGACY = { type: "ephemeral", ttl: "1h" };
594
604
 
595
605
  /**
596
606
  * Strip every cache_control marker from a single user message's content
@@ -777,7 +787,9 @@ const CACHE_CONTROL_STICKY_DIR = join(homedir(), ".claude", "cache-fix-state");
777
787
  // CC uses 1 on system[2] + cache_control_normalize places 1 on last user msg = 2 reserved.
778
788
  // Sticky can use at most 2 historical positions to stay within the 4-marker cap.
779
789
  const CACHE_CONTROL_STICKY_MAX_POSITIONS = 2;
780
- const CACHE_CONTROL_STICKY_DEFAULT_MARKER = { type: "ephemeral", ttl: "1h" };
790
+ function getCacheControlStickyDefaultMarker() {
791
+ return { type: "ephemeral", ttl: _detectedTtlTier };
792
+ }
781
793
 
782
794
  /**
783
795
  * Build the absolute state-file path for a given project key. Exported so
@@ -850,7 +862,7 @@ function readCacheControlStickyState(key) {
850
862
  marker:
851
863
  p.marker && typeof p.marker === "object" && typeof p.marker.type === "string"
852
864
  ? { ...p.marker }
853
- : { ...CACHE_CONTROL_STICKY_DEFAULT_MARKER },
865
+ : { ...getCacheControlStickyDefaultMarker() },
854
866
  });
855
867
  }
856
868
  return { version: 1, positions };
@@ -1800,6 +1812,24 @@ globalThis.fetch = async function (url, options) {
1800
1812
  debugLog("CACHE_FIX_DISABLED=1 — all bug fixes bypassed, monitoring active");
1801
1813
  }
1802
1814
 
1815
+ // Detect existing TTL tier from the payload. If any block already has
1816
+ // ttl="5m" (Q5h=100% tier), all injected markers must use 5m too —
1817
+ // the API rejects 1h after 5m in processing order (tools → system → messages).
1818
+ _detectedTtlTier = "1h";
1819
+ const allBlocks = [
1820
+ ...(Array.isArray(payload.system) ? payload.system : []),
1821
+ ...(Array.isArray(payload.messages) ? payload.messages.flatMap(m => Array.isArray(m.content) ? m.content : []) : []),
1822
+ ];
1823
+ for (const block of allBlocks) {
1824
+ if (block?.cache_control?.ttl === "5m") {
1825
+ _detectedTtlTier = "5m";
1826
+ break;
1827
+ }
1828
+ }
1829
+ if (_detectedTtlTier === "5m") {
1830
+ debugLog("TTL TIER DETECT: existing 5m markers found — all injected markers will use 5m");
1831
+ }
1832
+
1803
1833
  debugLog("--- API call to", urlStr);
1804
1834
  debugLog("message count:", payload.messages?.length);
1805
1835
 
@@ -2350,15 +2380,15 @@ globalThis.fetch = async function (url, options) {
2350
2380
  const existingCC = targetBlock?.cache_control;
2351
2381
  const canonicalAlreadyCorrect =
2352
2382
  existingCC &&
2353
- existingCC.type === CACHE_CONTROL_CANONICAL_MARKER.type &&
2354
- existingCC.ttl === CACHE_CONTROL_CANONICAL_MARKER.ttl;
2383
+ existingCC.type === getCanonicalMarker().type &&
2384
+ existingCC.ttl === getCanonicalMarker().ttl;
2355
2385
 
2356
2386
  if (!(canonicalAlreadyCorrect && countUserCacheControlMarkers(payload) === 1)) {
2357
2387
  // Strip all markers from user messages, then place canonical.
2358
2388
  for (const msg of payload.messages) stripCacheControlMarkers(msg);
2359
2389
  const tm = payload.messages[targetMsgIdx];
2360
2390
  const newContent = tm.content.slice();
2361
- newContent[targetBlockIdx] = { ...newContent[targetBlockIdx], cache_control: { ...CACHE_CONTROL_CANONICAL_MARKER } };
2391
+ newContent[targetBlockIdx] = { ...newContent[targetBlockIdx], cache_control: { ...getCanonicalMarker() } };
2362
2392
  payload.messages[targetMsgIdx] = { ...tm, content: newContent };
2363
2393
  ccMutated = true;
2364
2394
  }
@@ -2423,7 +2453,8 @@ globalThis.fetch = async function (url, options) {
2423
2453
  debugLog(`SKIPPED: TTL injection (${requestType} set to 'none' — pass-through)`);
2424
2454
  recordFixResult("ttl", "skipped");
2425
2455
  } else {
2426
- const ttlParam = ttlValue === "5m" ? "5m" : "1h";
2456
+ // Respect detected tier: if existing blocks have 5m, never inject 1h
2457
+ const ttlParam = ttlValue === "5m" || _detectedTtlTier === "5m" ? "5m" : "1h";
2427
2458
  let ttlInjected = 0;
2428
2459
  payload.system = payload.system.map((block) => {
2429
2460
  if (block.cache_control?.type === "ephemeral" && !block.cache_control.ttl) {
@@ -2835,7 +2866,8 @@ export {
2835
2866
  isBookkeepingReminder,
2836
2867
  stripCacheControlMarkers,
2837
2868
  countUserCacheControlMarkers,
2838
- CACHE_CONTROL_CANONICAL_MARKER,
2869
+ CACHE_CONTROL_CANONICAL_MARKER_LEGACY,
2870
+ getCanonicalMarker,
2839
2871
  normalizeToolUseInputsInBody,
2840
2872
  computeStickyMessageHash,
2841
2873
  cacheControlStickyStatePath,
@@ -2844,6 +2876,6 @@ export {
2844
2876
  readCacheControlStickyState,
2845
2877
  writeCacheControlStickyState,
2846
2878
  CACHE_CONTROL_STICKY_MAX_POSITIONS,
2847
- CACHE_CONTROL_STICKY_DEFAULT_MARKER,
2879
+ getCacheControlStickyDefaultMarker,
2848
2880
  _pinnedBlocks, // exported so tests can reset between runs
2849
2881
  };
@@ -46,8 +46,8 @@ USER_CONTEXT_FILE="${2:-}"
46
46
  if [ -d "$INPUT" ]; then
47
47
  # Convert project directory to CC's project path format
48
48
  REAL_PATH=$(realpath "$INPUT")
49
- # CC replaces / with - and prepends -
50
- PROJECT_KEY=$(echo "$REAL_PATH" | sed 's|/|-|g')
49
+ # CC replaces / with - and underscores with - , then prepends -
50
+ PROJECT_KEY=$(echo "$REAL_PATH" | sed 's|/|-|g' | sed 's|_|-|g')
51
51
  PROJECT_DIR="$HOME/.claude/projects/${PROJECT_KEY}"
52
52
 
53
53
  if [ ! -d "$PROJECT_DIR" ]; then
@@ -208,5 +208,7 @@ SIZE=$(wc -c < "$OUTPUT")
208
208
  echo ""
209
209
  echo "Summary generated: $OUTPUT ($SIZE bytes)"
210
210
  echo ""
211
- echo "To restore context after /clear:"
211
+ echo "To restore context after /clear, use this as your first message:"
212
+ echo ""
212
213
  echo " Read $OUTPUT for context on where we left off."
214
+ echo ""