Skip to content

Windows: same folder still splits into two project cards when seen by Claude Code and Copilot/Cursor #293

Description

@VasiHemanth

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.

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