Repository navigation
Content path mapping: narrow pathSegment class breaks cross-OS paths containing @, parentheses, spaces or non-ASCII #90
Description
Problem
pathSegment in internal/sync/paths.go bounds how far a matched path tail extends during content path mapping:
const pathSegment = `[A-Za-z0-9_.-]*`The class is narrower than a real file name. A space, @, (, ), +, ~, =, , and every non-ASCII rune all end a segment. The tail stops there, and the remainder keeps the pushing device's separators, so a cross-OS pull writes literal backslashes inside the path:
windows push: {"p":"C:\\work\\app\\node_modules\\@babel\\core\\index.js"}
remote: {"p":"${WORK}/app/node_modules/@babel\\core\\index.js"}
posix pull: {"p":"/Users/alice/projects/app/node_modules/@babel\core\index.js"}
The JSON stays valid, so this fails quietly. The path is simply wrong. Reproduced the same way for:
src\(auth)\page.tsx— Next.js route groupsC:\work\My Documents\a.txt— a spaceC:\work\プロジェクト\a.txt— non-ASCIIa+b\c.ts
Scoped npm packages put @ in essentially every JavaScript transcript, so this is the common case rather than a corner. When the character lands in the cwd field itself (C:\work\(internal)\app), the resolved cwd points at a directory that does not exist — the claude --resume failure the feature exists to prevent.
Why it wasn't fixed in #82
#82 made content mapping separator-aware and fixed the hard failure: main writes invalid JSON when a Windows device pulls a POSIX-authored transcript. This is residual, and strictly narrower than the status quo — on main, Windows-authored content is never normalized at all, so Windows→POSIX is wholly broken.
The fix is a judgement call, not a mechanical change. Widening the class makes the tail reach further into quoted tool output and prose, which is how an early revision of #82 came to rewrite a PowerShell error message quoted inside a real transcript. That tradeoff deserves its own change.
Options
- Widen the segment class to "anything but a separator, a quote and whitespace". Bounded by the exclusions, so prose is still protected by the whitespace stop, and it fixes
@,(,)and non-ASCII. Does not fix a path containing a space. - Canonicalize separators across the whole JSON string value containing the match, rather than stopping at a regex boundary. Correct for any file name, but requires understanding where the string value ends — more machinery, and only well defined for
pathContentJSON. - Scan the tail in Go rather than in the regex: match the root with the cheap root-only pattern, then walk forward applying whatever predicate we want. This also removes the tail pattern that costs ~2× in
NormalizeContent, so it could be a win on both axes.
Option 3 looks best to me — it decouples the matching predicate from RE2's class syntax and recovers the performance — but it is a rewrite of the matching loop rather than a tweak.
Notes
internal/sync/paths.go— thepathSegmentcomment records the limitation and the blast radius.- Remote keys are unaffected:
EncodeClaudePathflattens every non-alphanumeric character, so sessions still land in the right directory. Only file content is wrong. TestPosixContentMappingIsBackwardCompatiblepins POSIX content as byte-identical to the pre-fix: make content path mapping separator- and escaping-aware #82 mapping, so any fix here must keep that passing.
Activity
fix: make content path mapping separator- and escaping-aware (#82)