Skip to content

Content path mapping: narrow pathSegment class breaks cross-OS paths containing @, parentheses, spaces or non-ASCII #90

Open

Description

@tawanorg

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 groups
  • C:\work\My Documents\a.txt — a space
  • C:\work\プロジェクト\a.txt — non-ASCII
  • a+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

  1. 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.
  2. 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.
  3. 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 — the pathSegment comment records the limitation and the blast radius.
  • Remote keys are unaffected: EncodeClaudePath flattens every non-alphanumeric character, so sessions still land in the right directory. Only file content is wrong.
  • TestPosixContentMappingIsBackwardCompatible pins 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions