Repository navigation
Fix workspace name resolution for paths containing underscores - #270
mostafaseyedan wants to merge 3 commits into
Conversation
Claude Code encodes underscores as hyphens when building the project directory name under ~/.claude/projects, but encodeComponentForMatch only normalized whitespace. Any path component containing an underscore could therefore never match a readdirSync entry, so resolution stopped at the first such level and the fallback used the entire unresolved tail as the workspace name. A user whose home directory is /home/my_name saw every Claude workspace labeled "my-name-AIOps" instead of "AIOps", which also prevents sessions from aggregating with the same project recorded by other harnesses. Normalize underscores alongside whitespace, and add a regression test covering a path component with underscores.
|
@microsoft-github-policy-service agre
…On Sun, Sep 20, 2026 at 8:32 PM microsoft-github-policy-service[bot] < ***@***.***> wrote:
*microsoft-github-policy-service[bot]* left a comment
(microsoft/AI-Engineering-Coach#270)
<#270 (comment)>
mostafaseyedan <https://github.com/mostafaseyedan> please read the
following Contributor License Agreement(CLA). If you agree with the CLA,
please reply with the following information.
@microsoft-github-policy-service agree [company="{your company}"]
Options:
- (default - no company specified) I have sole ownership of
intellectual property rights to my Submissions and I am not making
Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
- (when company given) I am making Submissions in the course of work
for my employer (or my employer has intellectual property rights in my
Submissions by contract or applicable law). I have permission from my
employer to make Submissions and enter into this Agreement on behalf of my
employer. By signing below, the defined term “You” includes me and my
employer.
@microsoft-github-policy-service agree company="Microsoft"
Contributor License Agreement Contribution License Agreement
This Contribution License Agreement (*“Agreement”*) is agreed to by the
party signing below (*“You”*),
and conveys certain license rights to Microsoft Corporation and its
affiliates (“Microsoft”) for Your
contributions to Microsoft open source projects. This Agreement is
effective as of the latest signature
date below.
1. *Definitions*.
*“Code”* means the computer software code, whether in human-readable
or machine-executable form,
that is delivered by You to Microsoft under this Agreement.
*“Project”* means any of the projects owned or managed by Microsoft
and offered under a license
approved by the Open Source Initiative (www.opensource.org).
*“Submit”* is the act of uploading, submitting, transmitting, or
distributing code or other content to any
Project, including but not limited to communication on electronic
mailing lists, source code control
systems, and issue tracking systems that are managed by, or on behalf
of, the Project for the purpose of
discussing and improving that Project, but excluding communication
that is conspicuously marked or
otherwise designated in writing by You as “Not a Submission.”
*“Submission”* means the Code and any other copyrightable material
Submitted by You, including any
associated comments and documentation.
2. *Your Submission*. You must agree to the terms of this Agreement
before making a Submission to any
Project. This Agreement covers any and all Submissions that You, now
or in the future (except as
described in Section 4 below), Submit to any Project.
3. *Originality of Work*. You represent that each of Your Submissions
is entirely Your original work.
Should You wish to Submit materials that are not Your original work,
You may Submit them separately
to the Project if You (a) retain all copyright and license information
that was in the materials as You
received them, (b) in the description accompanying Your Submission,
include the phrase “Submission
containing materials of a third party:” followed by the names of the
third party and any licenses or other
restrictions of which You are aware, and (c) follow any other
instructions in the Project’s written
guidelines concerning Submissions.
4. *Your Employer*. References to “employer” in this Agreement include
Your employer or anyone else
for whom You are acting in making Your Submission, e.g. as a
contractor, vendor, or agent. If Your
Submission is made in the course of Your work for an employer or Your
employer has intellectual
property rights in Your Submission by contract or applicable law, You
must secure permission from Your
employer to make the Submission before signing this Agreement. In that
case, the term “You” in this
Agreement will refer to You and the employer collectively. If You
change employers in the future and
desire to Submit additional Submissions for the new employer, then You
agree to sign a new Agreement
and secure permission from the new employer before Submitting those
Submissions.
5. *Licenses*.
- *Copyright License*. You grant Microsoft, and those who receive the
Submission directly or
indirectly from Microsoft, a perpetual, worldwide, non-exclusive,
royalty-free, irrevocable license in the
Submission to reproduce, prepare derivative works of, publicly
display, publicly perform, and distribute
the Submission and such derivative works, and to sublicense any or all
of the foregoing rights to third
parties.
- *Patent License*. You grant Microsoft, and those who receive the
Submission directly or
indirectly from Microsoft, a perpetual, worldwide, non-exclusive,
royalty-free, irrevocable license under
Your patent claims that are necessarily infringed by the Submission or
the combination of the
Submission with the Project to which it was Submitted to make, have
made, use, offer to sell, sell and
import or otherwise dispose of the Submission alone or with the
Project.
- *Other Rights Reserved*. Each party reserves all rights not
expressly granted in this Agreement.
No additional licenses or rights whatsoever (including, without
limitation, any implied licenses) are
granted by implication, exhaustion, estoppel or otherwise.
6. *Representations and Warranties*. You represent that You are
legally entitled to grant the above
licenses. You represent that each of Your Submissions is entirely Your
original work (except as You may
have disclosed under Section 3). You represent that You have secured
permission from Your employer to
make the Submission in cases where Your Submission is made in the
course of Your work for Your
employer or Your employer has intellectual property rights in Your
Submission by contract or applicable
law. If You are signing this Agreement on behalf of Your employer, You
represent and warrant that You
have the necessary authority to bind the listed employer to the
obligations contained in this Agreement.
You are not expected to provide support for Your Submission, unless
You choose to do so. UNLESS
REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE
WARRANTIES
EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED
UNDER THIS AGREEMENT IS
PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO,
ANY WARRANTY OF
NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
7. *Notice to Microsoft*. You agree to notify Microsoft in writing of
any facts or circumstances of which
You later become aware that would make Your representations in this
Agreement inaccurate in any
respect.
8. *Information about Submissions*. You agree that contributions to
Projects and information about
contributions may be maintained indefinitely and disclosed publicly,
including Your name and other
information that You submit with Your Submission.
9. *Governing Law/Jurisdiction*. This Agreement is governed by the
laws of the State of Washington, and
the parties consent to exclusive jurisdiction and venue in the federal
courts sitting in King County,
Washington, unless no federal subject matter jurisdiction exists, in
which case the parties consent to
exclusive jurisdiction and venue in the Superior Court of King County,
Washington. The parties waive all
defenses of lack of personal jurisdiction and forum non-conveniens.
10. *Entire Agreement/Assignment*. This Agreement is the entire
agreement between the parties, and
supersedes any and all prior agreements, understandings or
communications, written or oral, between
the parties relating to the subject matter hereof. This Agreement may
be assigned by Microsoft.
—
Reply to this email directly, view it on GitHub
<#270?email_source=notifications&email_token=A5PZOP36HJHBMYO2N34UJV35QCAKRA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNZVGQZDMMRZGM32M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5754262937>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/A5PZOP7NRDE2ZTGGMLEDLVD5QCAKRAVCNFSNUABGKJSXA33TNF2G64TZHMYTEMZRGI4TCNZWGQ5US43TOVSTWNJVGIYTONJYHE2TBILWAI>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
|
@microsoft-github-policy-service agree
On Sun, Sep 20, 2026 at 9:06 PM Mohammad Alsayyedan <
***@***.***> wrote:
… @microsoft-github-policy-service agre
On Sun, Sep 20, 2026 at 8:32 PM microsoft-github-policy-service[bot] <
***@***.***> wrote:
> *microsoft-github-policy-service[bot]* left a comment
> (microsoft/AI-Engineering-Coach#270)
> <#270 (comment)>
>
> mostafaseyedan <https://github.com/mostafaseyedan> please read the
> following Contributor License Agreement(CLA). If you agree with the CLA,
> please reply with the following information.
>
> @microsoft-github-policy-service agree [company="{your company}"]
>
> Options:
>
> - (default - no company specified) I have sole ownership of
> intellectual property rights to my Submissions and I am not making
> Submissions in the course of work for my employer.
>
> @microsoft-github-policy-service agree
>
>
> - (when company given) I am making Submissions in the course of work
> for my employer (or my employer has intellectual property rights in my
> Submissions by contract or applicable law). I have permission from my
> employer to make Submissions and enter into this Agreement on behalf of my
> employer. By signing below, the defined term “You” includes me and my
> employer.
>
> @microsoft-github-policy-service agree company="Microsoft"
>
> Contributor License Agreement Contribution License Agreement
>
> This Contribution License Agreement (*“Agreement”*) is agreed to by the
> party signing below (*“You”*),
> and conveys certain license rights to Microsoft Corporation and its
> affiliates (“Microsoft”) for Your
> contributions to Microsoft open source projects. This Agreement is
> effective as of the latest signature
> date below.
>
> 1. *Definitions*.
> *“Code”* means the computer software code, whether in human-readable
> or machine-executable form,
> that is delivered by You to Microsoft under this Agreement.
> *“Project”* means any of the projects owned or managed by Microsoft
> and offered under a license
> approved by the Open Source Initiative (www.opensource.org).
> *“Submit”* is the act of uploading, submitting, transmitting, or
> distributing code or other content to any
> Project, including but not limited to communication on electronic
> mailing lists, source code control
> systems, and issue tracking systems that are managed by, or on behalf
> of, the Project for the purpose of
> discussing and improving that Project, but excluding communication
> that is conspicuously marked or
> otherwise designated in writing by You as “Not a Submission.”
> *“Submission”* means the Code and any other copyrightable material
> Submitted by You, including any
> associated comments and documentation.
> 2. *Your Submission*. You must agree to the terms of this Agreement
> before making a Submission to any
> Project. This Agreement covers any and all Submissions that You, now
> or in the future (except as
> described in Section 4 below), Submit to any Project.
> 3. *Originality of Work*. You represent that each of Your Submissions
> is entirely Your original work.
> Should You wish to Submit materials that are not Your original work,
> You may Submit them separately
> to the Project if You (a) retain all copyright and license
> information that was in the materials as You
> received them, (b) in the description accompanying Your Submission,
> include the phrase “Submission
> containing materials of a third party:” followed by the names of the
> third party and any licenses or other
> restrictions of which You are aware, and (c) follow any other
> instructions in the Project’s written
> guidelines concerning Submissions.
> 4. *Your Employer*. References to “employer” in this Agreement
> include Your employer or anyone else
> for whom You are acting in making Your Submission, e.g. as a
> contractor, vendor, or agent. If Your
> Submission is made in the course of Your work for an employer or Your
> employer has intellectual
> property rights in Your Submission by contract or applicable law, You
> must secure permission from Your
> employer to make the Submission before signing this Agreement. In
> that case, the term “You” in this
> Agreement will refer to You and the employer collectively. If You
> change employers in the future and
> desire to Submit additional Submissions for the new employer, then
> You agree to sign a new Agreement
> and secure permission from the new employer before Submitting those
> Submissions.
> 5. *Licenses*.
>
>
> - *Copyright License*. You grant Microsoft, and those who receive the
> Submission directly or
> indirectly from Microsoft, a perpetual, worldwide, non-exclusive,
> royalty-free, irrevocable license in the
> Submission to reproduce, prepare derivative works of, publicly
> display, publicly perform, and distribute
> the Submission and such derivative works, and to sublicense any or
> all of the foregoing rights to third
> parties.
> - *Patent License*. You grant Microsoft, and those who receive the
> Submission directly or
> indirectly from Microsoft, a perpetual, worldwide, non-exclusive,
> royalty-free, irrevocable license under
> Your patent claims that are necessarily infringed by the Submission
> or the combination of the
> Submission with the Project to which it was Submitted to make, have
> made, use, offer to sell, sell and
> import or otherwise dispose of the Submission alone or with the
> Project.
> - *Other Rights Reserved*. Each party reserves all rights not
> expressly granted in this Agreement.
> No additional licenses or rights whatsoever (including, without
> limitation, any implied licenses) are
> granted by implication, exhaustion, estoppel or otherwise.
>
>
> 6. *Representations and Warranties*. You represent that You are
> legally entitled to grant the above
> licenses. You represent that each of Your Submissions is entirely
> Your original work (except as You may
> have disclosed under Section 3). You represent that You have secured
> permission from Your employer to
> make the Submission in cases where Your Submission is made in the
> course of Your work for Your
> employer or Your employer has intellectual property rights in Your
> Submission by contract or applicable
> law. If You are signing this Agreement on behalf of Your employer,
> You represent and warrant that You
> have the necessary authority to bind the listed employer to the
> obligations contained in this Agreement.
> You are not expected to provide support for Your Submission, unless
> You choose to do so. UNLESS
> REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR
> THE WARRANTIES
> EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED
> UNDER THIS AGREEMENT IS
> PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO,
> ANY WARRANTY OF
> NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
> 7. *Notice to Microsoft*. You agree to notify Microsoft in writing of
> any facts or circumstances of which
> You later become aware that would make Your representations in this
> Agreement inaccurate in any
> respect.
> 8. *Information about Submissions*. You agree that contributions to
> Projects and information about
> contributions may be maintained indefinitely and disclosed publicly,
> including Your name and other
> information that You submit with Your Submission.
> 9. *Governing Law/Jurisdiction*. This Agreement is governed by the
> laws of the State of Washington, and
> the parties consent to exclusive jurisdiction and venue in the
> federal courts sitting in King County,
> Washington, unless no federal subject matter jurisdiction exists, in
> which case the parties consent to
> exclusive jurisdiction and venue in the Superior Court of King
> County, Washington. The parties waive all
> defenses of lack of personal jurisdiction and forum non-conveniens.
> 10. *Entire Agreement/Assignment*. This Agreement is the entire
> agreement between the parties, and
> supersedes any and all prior agreements, understandings or
> communications, written or oral, between
> the parties relating to the subject matter hereof. This Agreement may
> be assigned by Microsoft.
>
> —
> Reply to this email directly, view it on GitHub
> <#270?email_source=notifications&email_token=A5PZOP36HJHBMYO2N34UJV35QCAKRA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNZVGQZDMMRZGM32M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5754262937>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/A5PZOP7NRDE2ZTGGMLEDLVD5QCAKRAVCNFSNUABGKJSXA33TNF2G64TZHMYTEMZRGI4TCNZWGQ5US43TOVSTWNJVGIYTONJYHE2TBILWAI>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>
|
|
@microsoft-github-policy-service agree
…On Sun, Sep 20, 2026 at 9:07 PM microsoft-github-policy-service[bot] < ***@***.***> wrote:
*microsoft-github-policy-service[bot]* left a comment
(microsoft/AI-Engineering-Coach#270)
<#270 (comment)>
mostafaseyedan <https://github.com/mostafaseyedan> the command you issued
was incorrect. Please try again.
Examples are:
@microsoft-github-policy-service agree
and
@microsoft-github-policy-service agree company="your company"
—
Reply to this email directly, view it on GitHub
<#270?email_source=notifications&email_token=A5PZOP4KZFJOFDVW5WPWQGL5QCEPHA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNZVGQ2DQMRWGI32M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5754482627>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/A5PZOPZFMKP5TXW454ZGXM35QCEPHAVCNFSNUABGKJSXA33TNF2G64TZHMYTEMZRGI4TCNZWGQ5US43TOVSTWNJVGIYTONJYHE2TBILWAI>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
extractClaudeImagesFromJsonl is a standalone function, so its tests do not belong inside the parseClaudeSessions suite. Moving them also keeps this PR from editing the same lines as microsoft#270.
|
Hi Sanjay Singh (@san360) TamasBoncz Aymen (@aymenfurter), a single note for my five open PRs so I don't ping each thread:
The CLA check passes on all five. CI, CodeQL and Dependency Review are waiting for maintainer approval ("action_required") because this is my first contribution here, so none of them has run yet. Could someone approve the workflow runs? I ran the CI steps locally on each branch and on all five merged onto current
The PRs are independent and can be reviewed and merged separately. Happy to adjust anything. Thanks! |
|
Hello Sanjay Singh (@san360), TamasBoncz and Aymen (@aymenfurter), Thank you very much for your time and for the work you put into this project. I merged the latest If you have a moment, could you please approve those workflow runs? I will fix anything the checks report. Thank you again for your kind help. |
Sanjay Singh (san360)
left a comment
There was a problem hiding this comment.
Hi mostafaseyedan, thank you so much for this fix, and for the clear write-up! The before/after numbers from a real log set (38 → 30 workspaces, AIOps split across harnesses) made the impact immediately obvious, and pairing a two-line change with a regression test that mirrors the existing spaces test is exactly how we like these. Thanks as well for keeping the branch up to date with main.
What we verified
- Reproduced the bug on current
mainwith a synthetic~/.claude/projectslayout under a temporary home directory: a project at…/my_name/AIOpsis labelledmy-name-AIOpsfor Claude while Codex labels the same folderAIOps, so the dashboard shows two workspaces. With your change both areAIOpsand the sessions group together;workspaceIdis unchanged. - Your new test fails on
main(expected 'my-home-dir-proj' to be 'proj') and passes with the fix. - On
mainmerged with this PR: typecheck, lint (0 errors), knip, lockfile-lint, the full unit suite (3671 passed), build and the bundle-size check all pass. - We tried a range of other paths (hyphens, spaces, an underscore next to a hyphen,
my_namenext to amy-namesibling, drive-letter case, deleted folders, very long paths) and nothing that worked before broke. - We also created real Claude Code sessions in throwaway folders. With your change,
my_name/AIOpsandauto_user/AIOpsAutonow show asAIOpsandAIOpsAuto(they weremy-name-AIOpsandauto-user-AIOpsAuto). Sessions from the Claude Code CLI and from the VS Code extension land in the same workspace whatever the drive-letter case, and the names stay the same after a cached reload.work/auto.appandcafé_lab/projstill show asauto-appandcaf--lab-proj; the broader[^a-zA-Z0-9]regex below resolves both.
One suggestion: cover the rest of Claude Code's encoding
Claude Code doesn't only map _ to -: it replaces every character outside A–Z, a–z, 0–9 (one - per UTF-16 code unit), see anthropics/claude-code#19972. So the same bug still happens for folders with dots, other punctuation or non-ASCII letters:
| Project path | Expected | main |
This PR | With [^a-zA-Z0-9] |
|---|---|---|---|---|
…/my_name/AIOps |
AIOps |
my-name-AIOps |
AIOps |
AIOps |
…/work/my.app |
my.app |
my-app |
my-app |
my.app |
…/.config/tool |
tool |
-config-tool |
-config-tool |
tool |
…/v1.2/proj |
proj |
v1-2-proj |
v1-2-proj |
proj |
…/c@d+e/proj |
proj |
c-d-e-proj |
c-d-e-proj |
proj |
…/café/proj |
proj |
caf--proj |
caf--proj |
proj |
…/日本語/proj |
proj |
----proj |
----proj |
proj |
…/café_lab/proj |
proj |
caf--lab-proj |
caf--lab-proj |
proj |
We tried name.replaceAll(/[^a-zA-Z0-9]/g, '-') locally: it resolves all of the rows above, and the existing parser-claude tests still pass. Inline suggestions below, for the code and for the test. Would you be up for folding that into this PR? If you'd rather keep this one focused on underscores, that's completely fine too, and we can track the rest as a follow-up.
Small note on the description (no change needed)
The description mentions that _/- sibling ambiguity is mitigated by the longest-first sort. For siblings of equal length such as a_b and a-b, both encode to a-b, so the first readdirSync entry wins and the walk doesn't backtrack: with sib/a_b/x/proj and an empty sib/a-b/, the result is x-proj (same as on main, so not a regression). Just flagging it in case you want to reword that sentence; a backtracking walk could be a nice follow-up.
Optional idea for later
A few cases can't be recovered from the folder name at all: projects whose folder was deleted, paths over 200 characters (Claude truncates them and appends a hash), and logs created on another OS. Claude's transcript lines carry cwd, which the parser already reads for workspaceRootPath; deriving the name from basename(cwd) (falling back to the folder name) could handle those and avoid walking the filesystem. Definitely out of scope here, just sharing in case it's interesting.
Next steps
A maintainer will approve the CI run for this PR (it is waiting for approval, so CI, CodeQL and the spellcheck haven't run yet). Once it's green, this is in good shape to go in, with or without the broader regex.
Thanks again for taking the time to track this down and fix it, it makes the dashboard noticeably more accurate for everyone with an underscore in their path!
| /** Encode a single filesystem component the way Claude Code does: | ||
| * replace whitespace with hyphens. Path separators never appear in a single | ||
| * replace whitespace and underscores with hyphens. Path separators never appear in a single | ||
| * component name, and colons are not handled here because Windows component | ||
| * names cannot contain them. */ | ||
| function encodeComponentForMatch(name: string): string { | ||
| return name.replace(/\s/g, '-'); | ||
| return name.replace(/[\s_]/g, '-'); |
There was a problem hiding this comment.
Thanks for updating the comment along with the regex! Since Claude Code replaces every character outside [A-Za-z0-9] with - (anthropics/claude-code#19972), could we match that rule here? With this version, folders like my.app, .config, v1.2, c@d+e, café and 日本語 resolve too (they still fall back to the joined tail with [\s_]), and the existing parser-claude tests still pass. replaceAll also keeps the unicorn/prefer-string-replace-all lint rule happy.
| /** Encode a single filesystem component the way Claude Code does: | |
| * replace whitespace with hyphens. Path separators never appear in a single | |
| * replace whitespace and underscores with hyphens. Path separators never appear in a single | |
| * component name, and colons are not handled here because Windows component | |
| * names cannot contain them. */ | |
| function encodeComponentForMatch(name: string): string { | |
| return name.replace(/\s/g, '-'); | |
| return name.replace(/[\s_]/g, '-'); | |
| /** Encode a single filesystem component the way Claude Code does: | |
| * replace every character outside A-Z, a-z and 0-9 with a hyphen (one per | |
| * UTF-16 code unit). Path separators never appear in a single | |
| * component name, and colons are not handled here because Windows component | |
| * names cannot contain them. */ | |
| function encodeComponentForMatch(name: string): string { | |
| return name.replaceAll(/[^a-zA-Z0-9]/g, '-'); |
If you take this, the JSDoc on projectNameFromEncoded a few lines below ("replacing /, \, :, and whitespace") could get the same wording.
| const encodedDirName = targetDir | ||
| .replace(/^([a-zA-Z])(?=:)/, d => d.toLowerCase()) | ||
| .replace(/[:\\/\s_]/g, '-'); |
There was a problem hiding this comment.
Great to have this regression test! One idea: since the test builds the encoded name itself, using Claude Code's actual rule here (rather than the list of characters the extension handles) means the test would catch the remaining cases too:
| const encodedDirName = targetDir | |
| .replace(/^([a-zA-Z])(?=:)/, d => d.toLowerCase()) | |
| .replace(/[:\\/\s_]/g, '-'); | |
| const encodedDirName = targetDir | |
| .replace(/^([a-zA-Z])(?=:)/, d => d.toLowerCase()) | |
| .replaceAll(/[^a-zA-Z0-9]/g, '-'); |
And if you go with the broader regex, it could become table-driven over a few parent folder names, for example:
it.each(['my_home_dir', 'my.app', '.config', 'café'])(
'resolves workspace name when a parent folder is %s', { timeout: 30_000 }, (parentName) => {
const tmpBase = fs.mkdtempSync(path.join(longTmpDir(), 'claude-enc-'));
const targetDir = path.join(tmpBase, parentName, 'proj');
// … same body as above …
expect(result[0].workspaceName).toBe('proj');
},
);We tried both snippets locally: with the current [\s_] regex the my.app, .config and café rows fail, and with the suggested regex all of them pass.
Replace every character outside [A-Za-z0-9] with a hyphen, as Claude Code does, so folders with dots, punctuation or non-ASCII letters also resolve. Make the regression test table-driven over my_home_dir, my.app, .config and café. Suggested in review by @san360. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLDXsjE1DMDor8E6cSLBpH
|
Thank you very much for the thorough review, and for testing with real Claude Code sessions. That table made the remaining gap very clear. I folded in both suggestions:
I also corrected the description: the longest-first sort does not resolve equal-length siblings such as Typecheck and lint are clean on my side. Thanks again for your help! |
Problem
Claude Code replaces every character outside
[A-Za-z0-9]with a hyphen when building the project directory name under~/.claude/projects, butencodeComponentForMatchinsrc/core/parser-claude.tsonly normalized whitespace:A path component containing an underscore, dot, other punctuation or a non-ASCII letter can therefore never match a
readdirSyncentry.projectNameFromEncodedstops resolving at the first such level and falls through toresolved = path.join(resolved, rest), which takes the entire unresolved remainder as one segment.Impact
For a home directory of
/home/my_name, the encoded directory-home-my-name-AIOpsfails to match the real foldermy_name, so resolution dies at/homeand every Claude workspace is labeledmy-name-AIOpsinstead ofAIOps.Beyond the cosmetic mislabeling, this breaks cross-harness aggregation: the Codex and OpenCode parsers derive the plain basename, so the same project is counted as two separate workspaces and never reconciles. On one real log set (1010 sessions), this split
AIOpsinto 330 + 40 andcendien-websiteinto 201 + 50 + 7, and inflated the distinct workspace count from 30 to 38.This affects any user whose username or project path contains an underscore, a dot (for example
.config), other punctuation or non-ASCII letters.Fix
Apply Claude Code's own encoding rule (see anthropics/claude-code#19972), as suggested in review:
Known limit: sibling directories with equal-length names that encode the same (for example
a_banda-b) are still ambiguous. The firstreaddirSyncentry wins and the walk does not backtrack. This is the same as onmain, so it is not a regression.Testing
Adds a table-driven regression test mirroring the existing spaces test, over parent folders
my_home_dir,my.app,.configandcafé. Verified it fails onmain(expected 'my-home-dir-proj' to be 'proj') and passes with the fix.parser-claude.test.tspasses 22/22.npm run typecheckandnpm run lintare clean (0 errors). The 7 failures ingithub-app-analytics.test.tsare pre-existing onmainand unrelated.