claude-code-cache-fix 2.0.0-beta.2 → 2.0.0-beta.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/preload.mjs +71 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-cache-fix",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-beta.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",
package/preload.mjs CHANGED
@@ -727,6 +727,7 @@ const _STATS_SCHEMA = {
727
727
  git_status: { applied: 0, skipped: 0, lastApplied: null },
728
728
  cwd_normalize: { applied: 0, skipped: 0, lastApplied: null },
729
729
  smoosh_normalize: { applied: 0, skipped: 0, lastApplied: null },
730
+ smoosh_split: { applied: 0, skipped: 0, lastApplied: null },
730
731
  };
731
732
 
732
733
  function _createEmptyStats() {
@@ -1384,7 +1385,7 @@ globalThis.fetch = async function (url, options) {
1384
1385
  if (block.type === "tool_result" && typeof block.content === "string" && block.content.includes("<system-reminder>")) {
1385
1386
  let newContent = block.content;
1386
1387
  for (let p = 0; p < smooshPatterns.length; p++) {
1387
- smooshPatterns[p].lastIndex = 0; // reset regex state
1388
+ smooshPatterns[p].lastIndex = 0;
1388
1389
  newContent = newContent.replace(smooshPatterns[p], smooshReplacements[p]);
1389
1390
  }
1390
1391
  if (newContent !== block.content) {
@@ -1392,6 +1393,18 @@ globalThis.fetch = async function (url, options) {
1392
1393
  smooshNormalized++;
1393
1394
  }
1394
1395
  }
1396
+ // Unsmooshed standalone text blocks with dynamic system-reminder content
1397
+ if (block.type === "text" && typeof block.text === "string" && block.text.startsWith("<system-reminder>")) {
1398
+ let newText = block.text;
1399
+ for (let p = 0; p < smooshPatterns.length; p++) {
1400
+ smooshPatterns[p].lastIndex = 0;
1401
+ newText = newText.replace(smooshPatterns[p], smooshReplacements[p]);
1402
+ }
1403
+ if (newText !== block.text) {
1404
+ msg.content[i] = { ...block, text: newText };
1405
+ smooshNormalized++;
1406
+ }
1407
+ }
1395
1408
  }
1396
1409
  }
1397
1410
  }
@@ -1404,6 +1417,63 @@ globalThis.fetch = async function (url, options) {
1404
1417
  }
1405
1418
  }
1406
1419
 
1420
+ // Extension: smoosh_split — universal un-smoosh, complements smoosh_normalize.
1421
+ // CC's smooshSystemReminderSiblings (messages.ts:1835) folds any
1422
+ // `<system-reminder>`-prefixed text block adjacent to a tool_result
1423
+ // into that tool_result's content string with a leading `\n\n`.
1424
+ // The existing smoosh_normalize above stabilizes bytes for 4 enumerated
1425
+ // patterns (Token usage, USD budget, Output tokens, TodoWrite), but
1426
+ // hook-injected reminders (thinking-enrichment, action-tracker, MCP
1427
+ // deltas, custom user hooks) don't match those patterns and still drift.
1428
+ // smoosh_split peels any trailing `\n\n<system-reminder>...\n</system-reminder>`
1429
+ // off tool_result.content strings and restores it as a standalone text
1430
+ // block — the pre-smoosh shape. Dynamic drift in the peeled reminder
1431
+ // lives in a small block instead of a multi-KB tool_result string.
1432
+ // Composed with smoosh_normalize: normalize stabilizes known patterns
1433
+ // in-place; split peels any remainder. Full universal coverage.
1434
+ // Bug: anthropics/claude-code#49585
1435
+ // Opt-out via CACHE_FIX_SKIP_SMOOSH_SPLIT=1 (defaults ON).
1436
+ if (shouldApplyFix("smoosh_split") && payload.messages) {
1437
+ const TRAILING_SMOOSH_TAIL = /\n\n(<system-reminder>\n(?:(?!<\/system-reminder>)[\s\S])*?\n<\/system-reminder>)\s*$/;
1438
+ let splitApplied = 0;
1439
+ for (const msg of payload.messages) {
1440
+ if (msg.role !== "user" || !Array.isArray(msg.content)) continue;
1441
+ const out = [];
1442
+ let mutated = false;
1443
+ const peeledReminders = [];
1444
+ for (const block of msg.content) {
1445
+ if (block?.type === "tool_result" && typeof block.content === "string") {
1446
+ const reminders = [];
1447
+ let s = block.content;
1448
+ while (true) {
1449
+ const m = s.match(TRAILING_SMOOSH_TAIL);
1450
+ if (!m) break;
1451
+ reminders.unshift(m[1]);
1452
+ s = s.slice(0, m.index);
1453
+ }
1454
+ if (reminders.length > 0) {
1455
+ out.push({ ...block, content: s });
1456
+ for (const r of reminders) peeledReminders.push({ type: "text", text: r });
1457
+ splitApplied += reminders.length;
1458
+ mutated = true;
1459
+ continue;
1460
+ }
1461
+ }
1462
+ out.push(block);
1463
+ }
1464
+ // Peeled reminders go AFTER all other blocks so tool_results stay
1465
+ // consecutive (avoids API 400 "tool use concurrency" errors).
1466
+ if (mutated) msg.content = [...out, ...peeledReminders];
1467
+ }
1468
+ if (splitApplied > 0) {
1469
+ modified = true;
1470
+ debugLog(`APPLIED: smoosh-split peeled ${splitApplied} trailing system-reminder(s) from tool_result.content`);
1471
+ recordFixResult("smoosh_split", "applied");
1472
+ } else {
1473
+ recordFixResult("smoosh_split", "skipped");
1474
+ }
1475
+ }
1476
+
1407
1477
  // Bug 5: TTL enforcement (configurable per request type)
1408
1478
  // The client gates 1h cache TTL behind a GrowthBook allowlist that checks
1409
1479
  // querySource against patterns like "repl_main_thread*", "sdk", "auto_mode".