Summary
normalizePath in lib/protected-patterns.ts is a no-op on Windows, because its search string is two backslashes rather than one:
function normalizePath(input: string): string {
return input.replaceAll("\\\\", "/")
}
In TypeScript source, "\\\\" is the two-character string \\. A real Windows path contains single separators (C:\repo\src\secrets.ts), so nothing is ever replaced. The function exists only to make Windows paths comparable to forward-slash glob patterns, and it does not do that on the one platform that needs it.
The result is that protectedFilePatterns protects a file on macOS/Linux and silently fails to protect the same file on Windows.
Reproduction
import { matchesGlob, isFilePathProtected, getFilePathsFromParameters } from "./lib/protected-patterns"
const winPath = "C:\\repo\\src\\config\\secrets.ts"
const posixPath = "C:/repo/src/config/secrets.ts"
matchesGlob(posixPath, "**/secrets.ts") // -> true
matchesGlob(winPath, "**/secrets.ts") // -> false <-- same file
// End to end, through the shape a `read` tool call actually has:
const patterns = ["**/secrets.ts", "**/.env"]
isFilePathProtected(getFilePathsFromParameters("read", { filePath: posixPath }), patterns) // -> true
isFilePathProtected(getFilePathsFromParameters("read", { filePath: winPath }), patterns) // -> false
Measured behaviour for one file spelled two ways:
| pattern |
POSIX path |
Windows path |
**/secrets.ts |
true |
false |
**/config/*.ts |
true |
false |
C:/repo/src/** |
true |
false |
**/*.ts |
true |
true |
The last row is why this is easy to miss: a pattern with no separator after the ** still matches, because ** compiles to .* and happily spans backslashes. So a user testing with **/*.ts sees protection working, while every pattern that names a directory or an exact file quietly does nothing.
Impact
isFilePathProtected is the guard that keeps configured files out of pruning. It is consulted from four places:
lib/compress/protected-content.ts:138
lib/commands/sweep.ts:184 and :201
lib/strategies/deduplication.ts:60
lib/strategies/purge-errors.ts:62
On Windows all five call sites see false for the paths the user explicitly asked to protect, so those tool outputs are eligible for pruning and compression like any other. The failure is silent — the config parses, the schema validates, and nothing logs a mismatch. A user cannot tell from the outside that the setting is inert.
README.md:115 and dcp.schema.json:129 both document these patterns with forward slashes (**/*.config.ts), which is the correct spelling; the defect is purely that Windows paths never get converted to it.
Root cause
An escaping mistake, nothing more. replaceAll takes a string (not a regex) as its first argument, so no regex-level escaping is wanted there — "\\" is already the single backslash. The extra pair makes it search for a doubled separator, which only appears in an already-escaped string, never in a path from the filesystem.
Proposed fix
return input.replaceAll("\\", "/")
One character. Both the path and the pattern go through normalizePath, so this also makes a pattern written with Windows separators work, which is what a user copying a path out of Explorer would naturally produce.
Worth noting what must not change: separators have to be converted, not removed. * compiles to [^/]* and is expected not to cross a directory boundary, so src/* must still not match src\config\secrets.ts. That property is easy to break with a fix that strips backslashes instead of translating them, so it is worth a test of its own.
lib/protected-patterns.ts currently has no test file. PR to follow with one.
Summary
normalizePathinlib/protected-patterns.tsis a no-op on Windows, because its search string is two backslashes rather than one:In TypeScript source,
"\\\\"is the two-character string\\. A real Windows path contains single separators (C:\repo\src\secrets.ts), so nothing is ever replaced. The function exists only to make Windows paths comparable to forward-slash glob patterns, and it does not do that on the one platform that needs it.The result is that
protectedFilePatternsprotects a file on macOS/Linux and silently fails to protect the same file on Windows.Reproduction
Measured behaviour for one file spelled two ways:
**/secrets.tstruefalse**/config/*.tstruefalseC:/repo/src/**truefalse**/*.tstruetrueThe last row is why this is easy to miss: a pattern with no separator after the
**still matches, because**compiles to.*and happily spans backslashes. So a user testing with**/*.tssees protection working, while every pattern that names a directory or an exact file quietly does nothing.Impact
isFilePathProtectedis the guard that keeps configured files out of pruning. It is consulted from four places:lib/compress/protected-content.ts:138lib/commands/sweep.ts:184and:201lib/strategies/deduplication.ts:60lib/strategies/purge-errors.ts:62On Windows all five call sites see
falsefor the paths the user explicitly asked to protect, so those tool outputs are eligible for pruning and compression like any other. The failure is silent — the config parses, the schema validates, and nothing logs a mismatch. A user cannot tell from the outside that the setting is inert.README.md:115anddcp.schema.json:129both document these patterns with forward slashes (**/*.config.ts), which is the correct spelling; the defect is purely that Windows paths never get converted to it.Root cause
An escaping mistake, nothing more.
replaceAlltakes a string (not a regex) as its first argument, so no regex-level escaping is wanted there —"\\"is already the single backslash. The extra pair makes it search for a doubled separator, which only appears in an already-escaped string, never in a path from the filesystem.Proposed fix
One character. Both the path and the pattern go through
normalizePath, so this also makes a pattern written with Windows separators work, which is what a user copying a path out of Explorer would naturally produce.Worth noting what must not change: separators have to be converted, not removed.
*compiles to[^/]*and is expected not to cross a directory boundary, sosrc/*must still not matchsrc\config\secrets.ts. That property is easy to break with a fix that strips backslashes instead of translating them, so it is worth a test of its own.lib/protected-patterns.tscurrently has no test file. PR to follow with one.