Skip to content

feat: name Claude workspaces from the session cwd - #362

Open
mostafaseyedan wants to merge 1 commit into
microsoft:mainfrom
mostafaseyedan:feat/claude-workspace-name-from-cwd
Open

mostafaseyedan wants to merge 1 commit into
microsoft:mainfrom
mostafaseyedan:feat/claude-workspace-name-from-cwd

Conversation

@mostafaseyedan

Copy link
Copy Markdown

Problem

Claude workspace names come from decoding the project folder name under ~/.claude/projects and walking the disk to undo the lossy encoding. That walk cannot recover the name when:

  • the project folder was deleted or moved;
  • the path is longer than 200 characters, because Claude Code keeps the first 200 encoded characters and appends -<hash>;
  • the log was created on another OS, for example a Windows path read on Linux;
  • the session's top-level transcript is missing or skipped, so only sub-agent files remain.

In these cases the dashboard shows names such as c1aeb468-a558-4c55-bcdd-f4afeeef1098-scratchpad-empty or seg-kjgj8i instead of empty or proj.v1, and the sessions do not group with other harnesses.

Thank you, Sanjay Singh (@san360), for suggesting this in the #270 review!

Change

Claude transcript lines already carry cwd, which the parser reads for workspaceRootPath. parseClaudeProjectSessions now names the workspace from the basename of the first recorded cwd (from a session or a sub-agent file) that belongs to the project folder. Folder decoding stays as the fallback.

  • Belongs to the folder means that cwd encodes to the folder name with Claude Code's rule: every character outside [A-Za-z0-9] becomes - ([FEATURE/BUG] Improve project path encoding to preserve readability and improve third-party tool compatibility anthropics/claude-code#19972), compared without case. For a result over 200 characters, the folder must start with its first 200 characters plus -. I confirmed this rule with a real Claude Code run: a 281-character path produced a 207-character folder (<first 200>-kjgj8i). This check stops a cwd that points somewhere else from renaming the workspace.
  • path.win32.basename takes the last component, because it splits on both / and \.
  • workspaceId does not change, so cached data and grouping keys stay the same.

This is independent of #270. Both touch parser-claude.ts, but in different functions. #270 still matters for sessions without a cwd.

Verification

Unit tests: a new table-driven test in src/core/parser-claude.test.ts covers a deleted folder, a Windows path read on another OS, a truncated 200-character path, a project with only sub-agent files, and a cwd outside the project folder (fallback). The first four rows fail on main and pass with the change. The fallback row passes on both. parser-claude.test.ts: 31/31.

Real logs: I ran the parser on my own ~/.claude/projects (23 workspaces, Linux/WSL2, username with an underscore):

Project path main main + #270 This PR
/home/my_name/AIOps (and 12 more under home) my-name-AIOps AIOps AIOps
deleted /tmp/…/scratchpad/empty c1aeb468-…-scratchpad-empty c1aeb468-…-scratchpad-empty empty
281-character path ending in proj.v1 segment-01-…-seg-kjgj8i seg-kjgj8i proj.v1
/home/my_name/Apartment (both transcripts over the size limit, only sub-agent files parsed) my-name-Apartment Apartment Apartment

The other 7 workspaces keep the same name. Every session's workspaceName matches its workspace.

Checks: typecheck, lint (0 errors, no new warnings in the changed files), spellcheck, knip, build and the bundle-size check pass. In the full unit suite, 3674 tests pass. Two failures are unrelated:

  • analyzer-curiosity.test.ts "resets incomplete timing at action boundaries…" also fails on main in my time zone (CST) and passes with TZ=UTC.
  • curiosity.test.ts "evicts old entries…" timed out once under full-suite load and passes 47/47 alone.

I could not check: a real Claude Code session on Windows or macOS (the Windows case is covered by the unit test only).

Made with Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RLDXsjE1DMDor8E6cSLBpH

Claude transcripts record the session's cwd. When a recorded cwd encodes
to the project folder name with Claude Code's rule (every character
outside [A-Za-z0-9] becomes '-', and a result over 200 characters keeps
its first 200 plus '-<hash>'), use its basename as the workspace name.
Folder decoding stays as the fallback.

This recovers the name for deleted folders, truncated long paths, logs
from another OS, and projects with only sub-agent files. workspaceId is
unchanged.

Suggested by @san360 in the microsoft#270 review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLDXsjE1DMDor8E6cSLBpH

@san360 Sanjay Singh (san360) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much, mostafaseyedan, for this follow-up! Using the cwd that Claude Code already records is a neat way to recover the real folder name, and the cwdMatchesProjectDir guard keeps it safe: a cwd that points somewhere else can't rename a workspace. The table-driven test and the real-log table in the description made this very easy to review.

What we checked

  • Your new tests fail on current main (4 of the 5 rows) and pass with the change. With the PR merged on main, the full unit suite, typecheck, lint (no new warnings in the changed files), knip, lockfile lint, build and the bundle-size check all pass.

  • We ran real Claude Code sessions on Windows in throwaway repos and compared main with this PR:

    Project folder main This PR
    my_proj my-proj my_proj
    parent_dir\child parent-dir-child child
    proj.v1 proj-v1 proj.v1
    café caf- café
    gone_proj (deleted after the session) gone-proj gone_proj
    211-character path ending in proj.v1 segment-01-…-segment-fb9n8f proj.v1
    plain, My App, CaseRepo unchanged unchanged

    On Windows, the long path also produced a 207-character folder (<first 200>-fb9n8f), which confirms the truncation rule you found and covers the Windows run you couldn't do.

  • With a Codex session in the same folder, the Claude and Codex sessions now group under one my_proj workspace; main kept them apart.

  • We compared every dashboard page and tab between the two builds. The only differences are the renamed Claude workspaces and counts of distinct workspace names. workspaceId is unchanged, and a reload gives the same result.

Suggestions (small, none blocking)

  1. A test row for the case-insensitive match. Claude Code keeps the drive-letter case the session started with: starting from c:\… creates c--…, and a later session from C:\… writes into the same folder on Windows. We saw both sessions in one folder. That's exactly why cwdMatchesProjectDir lower-cases, but no test covers it yet: all five rows still pass if both .toLowerCase() calls are removed. There's a one-line suggestion inline.
  2. Optional: while loading, parseClaudeSessionsAsync still reports the folder-decoded name in the progress detail (onProject(i + 1, projectDirs.length, workspaceName), around line 766). The loading screen briefly shows my-proj, then the dashboard shows my_proj. It's purely cosmetic, so it's fine to leave it or do it later.
  3. Heads-up, no change needed: two different folders with the same last component now share one dashboard name in a few more cases. In our run, the deleted long path ending in proj.v1 grouped with another proj.v1 project. That matches how existing folders are already named, so it's consistent.

About #270: both PRs edit parser-claude.ts, but in different functions. They merge cleanly, and the Claude parser tests pass with both applied (35/35). #270 moves encodeComponentForMatch to the same "everything outside [A-Za-z0-9]" rule, so once both land, one shared helper could serve both. A follow-up for that would be welcome.

Next, a maintainer will approve the CI run for this PR.

Thanks again for following up on the #270 discussion so quickly, and for checking it so carefully!

{ label: 'a Windows path read on another OS', cwd: String.raw`C:\Users\me\My App`, expected: 'My App' },
{ label: 'a path Claude truncated to 200 characters', cwd: longCwd, expected: 'proj.v1' },
{ label: 'a session with only sub-agent files', cwd: '/home/my_name/gone_project', expected: 'gone_project', subagentOnly: true },
{ label: 'a cwd outside the project folder', cwd: '/elsewhere/other', dir: '-home-me-proj', expected: 'me-proj' },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a row for the case-insensitive match? Claude Code keeps the drive-letter case the session started with (c:\… → c--…), and on Windows a later C:\… session writes into the same folder. Today, removing the .toLowerCase() calls in cwdMatchesProjectDir keeps all five rows green. This row passes with the PR and fails on main:

Suggested change
{ label: 'a cwd outside the project folder', cwd: '/elsewhere/other', dir: '-home-me-proj', expected: 'me-proj' },
{ label: 'a drive letter in another case', cwd: String.raw`C:\Users\me\proj`, dir: 'c--Users-me-proj', expected: 'proj' },
{ label: 'a cwd outside the project folder', cwd: '/elsewhere/other', dir: '-home-me-proj', expected: 'me-proj' },

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants