Repository navigation
feat: name Claude workspaces from the session cwd - #362
mostafaseyedan wants to merge 1 commit into
Conversation
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
Sanjay Singh (san360)
left a comment
There was a problem hiding this comment.
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 onmain, 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
mainwith this PR:Project folder mainThis PR my_projmy-projmy_projparent_dir\childparent-dir-childchildproj.v1proj-v1proj.v1cafécaf-cafégone_proj(deleted after the session)gone-projgone_proj211-character path ending in proj.v1segment-01-…-segment-fb9n8fproj.v1plain,My App,CaseRepounchanged 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_projworkspace;mainkept 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.
workspaceIdis unchanged, and a reload gives the same result.
Suggestions (small, none blocking)
- A test row for the case-insensitive match. Claude Code keeps the drive-letter case the session started with: starting from
c:\…createsc--…, and a later session fromC:\…writes into the same folder on Windows. We saw both sessions in one folder. That's exactly whycwdMatchesProjectDirlower-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. - Optional: while loading,
parseClaudeSessionsAsyncstill reports the folder-decoded name in the progress detail (onProject(i + 1, projectDirs.length, workspaceName), around line 766). The loading screen briefly showsmy-proj, then the dashboard showsmy_proj. It's purely cosmetic, so it's fine to leave it or do it later. - 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.v1grouped with anotherproj.v1project. 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' }, |
There was a problem hiding this comment.
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:
| { 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' }, |
Problem
Claude workspace names come from decoding the project folder name under
~/.claude/projectsand walking the disk to undo the lossy encoding. That walk cannot recover the name when:-<hash>;In these cases the dashboard shows names such as
c1aeb468-a558-4c55-bcdd-f4afeeef1098-scratchpad-emptyorseg-kjgj8iinstead ofemptyorproj.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 forworkspaceRootPath.parseClaudeProjectSessionsnow names the workspace from the basename of the first recordedcwd(from a session or a sub-agent file) that belongs to the project folder. Folder decoding stays as the fallback.cwdencodes 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 acwdthat points somewhere else from renaming the workspace.path.win32.basenametakes the last component, because it splits on both/and\.workspaceIddoes 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 acwd.Verification
Unit tests: a new table-driven test in
src/core/parser-claude.test.tscovers a deleted folder, a Windows path read on another OS, a truncated 200-character path, a project with only sub-agent files, and acwdoutside the project folder (fallback). The first four rows fail onmainand 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):mainmain+ #270/home/my_name/AIOps(and 12 more under home)my-name-AIOpsAIOpsAIOps/tmp/…/scratchpad/emptyc1aeb468-…-scratchpad-emptyc1aeb468-…-scratchpad-emptyemptyproj.v1segment-01-…-seg-kjgj8iseg-kjgj8iproj.v1/home/my_name/Apartment(both transcripts over the size limit, only sub-agent files parsed)my-name-ApartmentApartmentApartmentThe other 7 workspaces keep the same name. Every session's
workspaceNamematches 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 onmainin my time zone (CST) and passes withTZ=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