Follow-up to #290, which folded C:\a\b and C:/a/b into one project identity. A second separator variant is still unhandled, and it is the one most likely to show up in practice: the same folder opened by two different agents.
What happens
The Copilot/VS Code and Cursor scanners do not read a cwd string. They derive the project path from the workspace file: URI:
project_path = unquote(folder_url.replace("file://", ""))
On Windows VS Code stores file:///c%3A/Users/dev/proj, which decodes to /c:/Users/dev/proj: leading slash, lowercase drive letter.
tt_paths.canonical_project() matches Windows paths with ^(?:[A-Za-z]:|\\\\). The leading slash means that anchor never matches, so the path passes through untouched and stays a separate identity from the C:/Users/dev/proj that Claude Code, Codex and the rest produce.
Reproduction
Seed one Claude Code session and one Copilot/VS Code session in the same Windows folder, then scan and read /projects:
agent=claude project='C:/Users/dev/proj'
agent=copilot project='/c:/Users/dev/proj'
/projects CARDS: 2
path='C:/Users/dev/proj' sessions=1
path='/c:/Users/dev/proj' sessions=1
Two cards, one real folder. Same symptom #290 fixed, different input shape.
This needs only two agents used in one folder, rather than one agent logging inconsistently, so it is probably the more common trigger of the two.
Suggested fix
Two lines inside the branch canonical_project() already has: strip a leading / when it precedes a drive letter, then upcase the drive letter.
The drive-letter case matters here and I think it is safe despite the docstring's current argument against folding case. That argument cites case-sensitive filesystems, but control only reaches this branch after ^[A-Za-z]: has matched, so the path is already established as Windows, where the drive letter is case-insensitive by definition. Folding only the drive letter inside that branch does not touch the rest of the path, which is what the argument is actually protecting.
Worth extending backend/test_project_paths.py with the /c:/... form and a cross-agent card-count case.
Unrelated smaller thing in the same helper
canonical_project("C:/") returns "C:". On Windows that means "current directory on drive C", not the drive root, so it is corruption rather than a missed fold. The existing guard covers the POSIX root (/, //) but not the drive root.
Follow-up to #290, which folded
C:\a\bandC:/a/binto one project identity. A second separator variant is still unhandled, and it is the one most likely to show up in practice: the same folder opened by two different agents.What happens
The Copilot/VS Code and Cursor scanners do not read a
cwdstring. They derive the project path from the workspacefile:URI:On Windows VS Code stores
file:///c%3A/Users/dev/proj, which decodes to/c:/Users/dev/proj: leading slash, lowercase drive letter.tt_paths.canonical_project()matches Windows paths with^(?:[A-Za-z]:|\\\\). The leading slash means that anchor never matches, so the path passes through untouched and stays a separate identity from theC:/Users/dev/projthat Claude Code, Codex and the rest produce.Reproduction
Seed one Claude Code session and one Copilot/VS Code session in the same Windows folder, then scan and read
/projects:Two cards, one real folder. Same symptom #290 fixed, different input shape.
This needs only two agents used in one folder, rather than one agent logging inconsistently, so it is probably the more common trigger of the two.
Suggested fix
Two lines inside the branch
canonical_project()already has: strip a leading/when it precedes a drive letter, then upcase the drive letter.The drive-letter case matters here and I think it is safe despite the docstring's current argument against folding case. That argument cites case-sensitive filesystems, but control only reaches this branch after
^[A-Za-z]:has matched, so the path is already established as Windows, where the drive letter is case-insensitive by definition. Folding only the drive letter inside that branch does not touch the rest of the path, which is what the argument is actually protecting.Worth extending
backend/test_project_paths.pywith the/c:/...form and a cross-agent card-count case.Unrelated smaller thing in the same helper
canonical_project("C:/")returns"C:". On Windows that means "current directory on drive C", not the drive root, so it is corruption rather than a missed fold. The existing guard covers the POSIX root (/,//) but not the drive root.