Skip to content

Commit 268f7bc

Browse files
cnighswongerclaude
andcommitted
v2.0.0-beta.1: CWD/path normalization for cross-worktree cache stability
New opt-in fix: CACHE_FIX_NORMALIZE_CWD=1 replaces volatile path content in system prompt with stable placeholders: - Primary working directory path - Additional working directory paths - CLAUDE.md / memory file path references ("Contents of /path/...") Paths change per project/worktree, busting the entire prefix cache. With normalization, the system prompt is byte-identical across working directories — enabling cache reuse across worktrees. Model can still discover paths via Bash (pwd, ls) when needed. Credit: @wadabum for the architectural analysis (anthropics/claude-code#48236) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 66b0f59 commit 268f7bc

2 files changed

Lines changed: 44 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "claude-code-cache-fix",
3-
"version": "1.11.0",
3+
"version": "2.0.0-beta.1",
44
"description": "Fixes prompt cache regression in Claude Code that causes up to 20x cost increase on resumed sessions",
55
"type": "module",
66
"exports": "./preload.mjs",

preload.mjs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,7 @@ const DEBUG = process.env.CACHE_FIX_DEBUG === "1";
680680
const PREFIXDIFF = process.env.CACHE_FIX_PREFIXDIFF === "1";
681681
const NORMALIZE_IDENTITY = process.env.CACHE_FIX_NORMALIZE_IDENTITY === "1";
682682
const STRIP_GIT_STATUS = process.env.CACHE_FIX_STRIP_GIT_STATUS === "1";
683+
const NORMALIZE_CWD = process.env.CACHE_FIX_NORMALIZE_CWD === "1";
683684
const TTL_MAIN = (process.env.CACHE_FIX_TTL_MAIN || "1h").toLowerCase();
684685
const TTL_SUBAGENT = (process.env.CACHE_FIX_TTL_SUBAGENT || "1h").toLowerCase();
685686
const LOG_PATH = join(homedir(), ".claude", "cache-fix-debug.log");
@@ -724,6 +725,7 @@ const _STATS_SCHEMA = {
724725
ttl: { applied: 0, skipped: 0, lastApplied: null },
725726
identity: { applied: 0, skipped: 0, lastApplied: null },
726727
git_status: { applied: 0, skipped: 0, lastApplied: null },
728+
cwd_normalize: { applied: 0, skipped: 0, lastApplied: null },
727729
};
728730

729731
function _createEmptyStats() {
@@ -1304,6 +1306,47 @@ globalThis.fetch = async function (url, options) {
13041306
}
13051307
}
13061308

1309+
// Optimization: normalize CWD and path references in system prompt
1310+
// CC injects the full working directory path, additional directories, and
1311+
// path references into system text blocks. These change per project/worktree,
1312+
// busting the prefix cache across different working directories.
1313+
// Opt-in via CACHE_FIX_NORMALIZE_CWD=1.
1314+
// The model can still discover paths via Bash (pwd, ls) when needed.
1315+
if (NORMALIZE_CWD && shouldApplyFix("cwd_normalize") && payload.system && Array.isArray(payload.system)) {
1316+
let normalized = 0;
1317+
payload.system = payload.system.map((block) => {
1318+
if (block?.type !== "text" || typeof block.text !== "string") return block;
1319+
let newText = block.text;
1320+
// Normalize "Primary working directory: /path/to/project"
1321+
newText = newText.replace(
1322+
/( - Primary working directory: ).+/g,
1323+
"$1[normalized by cache-fix]"
1324+
);
1325+
// Normalize "Additional working directories:" section
1326+
newText = newText.replace(
1327+
/( - Additional working directories:\n)((?: - .+\n)*)/g,
1328+
"$1 - [normalized by cache-fix]\n"
1329+
);
1330+
// Normalize "Contents of /path/to/..." in claudeMd/memory references
1331+
newText = newText.replace(
1332+
/Contents of \/[^\s(]+/g,
1333+
"Contents of [path normalized by cache-fix]"
1334+
);
1335+
if (newText !== block.text) {
1336+
normalized++;
1337+
return { ...block, text: newText };
1338+
}
1339+
return block;
1340+
});
1341+
if (normalized > 0) {
1342+
modified = true;
1343+
debugLog(`APPLIED: CWD/paths normalized in ${normalized} system block(s)`);
1344+
recordFixResult("cwd_normalize", "applied");
1345+
} else {
1346+
recordFixResult("cwd_normalize", "skipped");
1347+
}
1348+
}
1349+
13071350
// Bug 5: TTL enforcement (configurable per request type)
13081351
// The client gates 1h cache TTL behind a GrowthBook allowlist that checks
13091352
// querySource against patterns like "repl_main_thread*", "sdk", "auto_mode".

0 commit comments

Comments
 (0)