fix(stats): split Windows cwds on the backslash in attributeRepo - #858
Merged
jeff-r2026 merged 2 commits intoSep 28, 2026
Merged
Conversation
`attributeRepo` split a filesystem cwd on `/` only, so on Windows every `cwd` stayed one segment: `C:\Users\dev\work\teamai-cli` was attributed to a repo literally named `C:\Users\dev\work\teamai-cli` instead of `teamai-cli`. The `NON_REPO_LEAVES` check (`home`, `data`, `tmp`, ...) never fired either, so a session started in `C:\src\data` opened its own row instead of merging into `no_repo` like `/src/data` does. `repoLabel` masked most of it on Windows, because `path.basename` handles both separators and the repo-directory branch of `nameOf` runs first. The gap shows wherever the path is not a live repo directory: a worktree removed after the fact (Tencent#810), a session recorded outside git, or a dashboard event whose anchor no longer exists. A drive root (`C:\`) also left the bare drive letter as the leaf, where `/` on POSIX yields `no_repo`. Split on both separators and treat a bare drive letter as no project, so Windows cwds attribute the way POSIX ones already do. This also fixes `repoLabel`'s "a directory named like workspace is still no_repo" test on Windows, which failed on `origin/main` for the same reason.
The PR description includes a representative real-CLI end-to-end verification, so its testing record is sufficient. |
… child
`E2E (fork-safe, no credentials)` fails this suite on Linux with
Error: ENOTEMPTY: directory not empty, rmdir '/tmp/teamai-issue810-e2e-*/home/.teamai'
after all 12 tests pass, so the run reports `1 failed | 56 passed` suites with
`356 passed | 26 skipped` tests and exit code 1.
Every hook here goes through `hook-dispatch`, which spawns a detached child for
the background-only handlers (SessionEnd, Stop) — `spawnPlainDetached` in
src/hook-dispatch-cli.ts. The suite waits for the events that child writes, and
never for the child itself to exit, so `afterAll`'s removal runs while it may
still be creating a file under `.teamai`. One `rmdir` then loses the race and
the suite is reported failed with nothing actually wrong.
Add the retry options `git-kind-learnings.test.ts` already uses for the same
`git gc --auto` race. Node's rm retries EBUSY, EMFILE, ENFILE, ENOTEMPTY and
EPERM with a linear backoff, which covers both this and the Windows `EPERM`
this suite also hits locally.
Unrelated to the attributeRepo change in the parent commit: the suite never
calls it, and the diff is an identity transform on every POSIX-shaped cwd the
suite produces (verified over 35 inputs, including each `teamai-issue810-e2e-*`
path). The Windows `EPERM` failure is pre-existing — it reproduces identically
on a clean origin/main checkout — and persists here because it is a persistent
condition, not a transient one; this change does not make it worse.
The PR description includes a representative real-CLI end-to-end verification, so its testing record is sufficient. |
jeff-r2026
approved these changes
Sep 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
attributeRepo(src/utils/repo-attribution.ts) split a filesystemcwdon/only, so on Windows a path never got past its first segment:cwdC:\Users\dev\work\teamai-cliC:\Users\dev\work\teamai-cliteamai-cliD:\src\teamai-cliD:\src\teamai-cliteamai-cli\srv\share\new-api\srv\share\new-apinew-apiC:\Users\dev\homeC:\Users\dev\homeno_repoC:\src\dataC:\src\datano_repoC:\C:no_repoTwo consequences on Windows:
stats --by-repo, the dashboard andsession savelabelled every session with its whole absolute path, so no two sessions ever shared a repo row and the per-project breakdown was unusable.NON_REPO_LEAVES(home,root,users,tmp,workspace,srv,mnt,data,opt) never fired, because the "leaf" was the entire path. A session inC:\src\dataopened its own row where/src/datamerges intono_repo.repoLabelmasked most of this on Windows — its repo-directory branch callsrepoName, which goes throughpath.basenameand handles both separators. The gap is visible wherever the path is not a live repo directory: a worktree removed after the fact (#810), a session recorded outside git, or a dashboard event whose anchor no longer exists. A drive root (C:\) also left the bare drive letter as the leaf, where/on POSIX yieldsno_repo.This also fixes a pre-existing Windows failure in the same suite:
repoLabel's "names a repo after its directory even when that is a word like workspace" test builds its directories withpath.join, so on Windows they reachattributeRepowith backslashes and...\plain\datacame back asdatainstead ofno_repo.Changes
1.
attributeReposplits on both separators (src/utils/repo-attribution.ts)canonicalRepois untouched: its input is a remote form (github.com/o/r,git@host:o/r), which never contains a backslash, and splitting one on the backslash would only change a value that cannot occur.2. Retry the
#810e2e sandbox removal (src/__tests__/e2e/deleted-worktree-scope-810.test.ts)The
E2E (fork-safe, no credentials)check was failing this suite on Linux:All 12 tests passed; the suite-level failure is its
afterAll. Every hook here goes throughhook-dispatch, which spawns a detached child for the background-only handlers (SessionEnd, Stop) —spawnPlainDetachedinsrc/hook-dispatch-cli.ts. The suite waits for the events that child writes and never for the child to exit, so the removal runs while it may still be creating a file under.teamai, and onermdirloses the race.This is the retry
git-kind-learnings.test.ts:33already uses for the samegit gc --autorace, documented there in a comment. Node's rm retriesEBUSY,EMFILE,ENFILE,ENOTEMPTYandEPERMwith a linear backoff, which covers the WindowsEPERMthis suite also hits locally.This second change is independent of the first: the suite never calls
attributeRepo, and the diff is an identity transform on every POSIX-shaped cwd it produces — verified over 35 inputs, including eachteamai-issue810-e2e-*path the suite builds.Type of Change
No signature or output-contract change on POSIX: the existing
attributeRepoexpectations (/home/u/new-api,/opt/teamai-cli/,/home,/root,/opt,'',undefined) are unchanged and still pass. No docs describe the--by-repolabel rule, so there is no wording to sync;CHANGELOG.mdis generated bystandard-versionfrom commit messages.Test Plan
npx tsc --noEmitpasses (exit 0)npm run lintpasses (exit 0)npx vitest run— affected files pass (see below)New tests, red on
origin/main(a8ab8e0), verified by copying only the test file onto a cleanorigin/maincheckout and re-running:npx vitest run src/__tests__/session-analytics.test.ts src/__tests__/session-collector.test.ts src/__tests__/dashboard-collector.test.ts src/__tests__/dashboard-ui.test.ts src/__tests__/session-trends.test.ts: 5 files, exit 0, all passed (was 1 failed / 7 passed onorigin/mainfor the same set, the failure being therepoLabelone above).End-to-end (real CLI):
npm run build, thennpx vitest run --config vitest.e2e.config.ts src/__tests__/e2e/repo-attribution-worktrees-809.test.ts— 6 passed (15.5 s), coveringstats --by-reposhowing one row per repo across worktrees, labelling two same-named repos apart,session saverecording the repo as Project,import --dir/codebase --extractwiki slugs, and a removed worktree's sessions staying in the repo's dashboard workspace.The
#810suite, before and after the retry, on this Windows host:origin/main12 tests | 1 failed | 11 skipped12 tests | 1 failed | 11 skipped(identical)EPERMon cleanupThe remaining failure is a pre-existing Windows-only issue, unchanged by this PR:
git worktree remove --forceraisesEPERMon this host and CI on Linux never hits it. It reproduces identically on a cleanorigin/maincheckout. It persists after the retry because it is a persistent condition, not a transient one — the retry covers the transientENOTEMPTYCI actually reported, and does not make the Windows case worse.Not verified on this machine: the full
npx vitest run, and theE2Ejob's Linux behaviour end to end. On Windows the vitest/tinypool worker pool dies withERR_IPC_CHANNEL_CLOSEDpart-way through (reproduced on a cleanorigin/maincheckout too, so it predates this branch), anddashboard-report-scope.test.tscannot complete even alone onorigin/main(timed out at 110 s). CI runs the full suite on Linux; the files that consumeattributeRepoare covered individually above.