fix(session-analytics): discover Claude/Codex sessions in WSL installs too - #380
fix(session-analytics): discover Claude/Codex sessions in WSL installs too#380evolv3ai wants to merge 3 commits into
Conversation
…s too discoverSessionFiles built its Claude and Codex roots from os.homedir() only, so on a Windows host with a WSL install the session browser and its project list showed native sessions exclusively. sync already walks both Claude homes (src/commands/sync.js), which left WSL work counted in the token totals but invisible in the sessions/projects view -- the same gap also affects the `tracker sessions` CLI export, which shares this path. Build the roots via the existing wsl-probe helpers so TOKENTRACKER_WSL_MODE gates both sides consistently. Duplicate files synced between environments collapse via the Codex session-id pass and claudeMessageDedupKey downstream. WSL is probed only when `home` is the real os.homedir(). discoverWslHome resolves \wsl$ independently of `home`, so honouring an injected home keeps callers that pass their own (tests, custom HOME) hermetic. Verified on Windows 11 + WSL2 Ubuntu: sessions --from 2026-07-01, both: 1078 rows / 19 projects sessions --from 2026-07-01, native-only: 633 rows / 7 projects (unchanged) session-analytics + local-api suites green in both/native-only/wsl-first Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ation
Make providerRoots dependency-injectable (platform, homedir,
discoverWslHome) and export it, so the root selection can be exercised
without a WSL install on the host running the suite.
The isolation case is the one worth pinning: discoverWslHome resolves
\wsl$ independently of the `home` it is called alongside, so probing it
for an injected home (buildSessionAnalytics({ home }), a custom HOME)
splices the machine's live WSL transcripts into what the caller expects
to be an isolated fixture. Removing the guard fails that test and leaves
the other seven green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughSession analytics now supports native and WSL provider roots on Windows, applies ChangesWSL-aware session discovery
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant discoverSessionFiles
participant providerRoots
participant wslProbe
discoverSessionFiles->>providerRoots: resolve Claude and Codex roots
providerRoots->>wslProbe: discover optional WSL home
wslProbe-->>providerRoots: native and optional WSL roots
providerRoots-->>discoverSessionFiles: provider root lists
discoverSessionFiles->>discoverSessionFiles: scan and de-duplicate session files
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ec1b75a437
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Promise.all(codexRoots.map((r) => listRolloutFilesDeep(path.join(r, "archived_sessions")))), | ||
| listGrokSessionFiles(path.join(grokHome, "sessions")), | ||
| ]); | ||
| const allClaude = [...new Set(claudeGroups.flat())]; |
There was a problem hiding this comment.
Deduplicate copied Claude transcripts across roots
When ~/.claude is mirrored between Windows and WSL, this Set only removes identical path strings, so the native and UNC copies remain distinct. scanClaudeSession deduplicates messages only within one file, and mergeSessionFragments later sums rows with the same session hash; consequently every copied transcript doubles its tokens, cost, turns, and summary session count. Add the content-identity dedup already used by the sync pipeline before scanning these roots.
Useful? React with 👍 / 👎.
| if (platform === "win32" && isRealHome && wsl.shouldProbeWsl(env)) { | ||
| const wslRoot = discoverWslHome(providerDir, { env }); | ||
| if (wslRoot) roots.push(wslRoot); |
There was a problem hiding this comment.
Translate WSL session cwd values to UNC paths
When a newly discovered WSL transcript records a POSIX cwd such as /home/dev/project, both session scanners preserve that value as project_ref. Native Windows cannot resolve it: the browser copies an unusable directory for the required cd, and git-outcomes.js rejects it at fs.existsSync, so WSL sessions receive no Git attribution. Re-anchor WSL cwd values with the existing mapWslCwdToUnc(cwd, transcriptPath) helper when scanning files from this root.
Useful? React with 👍 / 👎.
| const { listClaudeProjectFiles, listRolloutFilesDeep, claudeMessageDedupKey } = require("./rollout"); | ||
| const { parseCodexRolloutFile } = require("./codex-rollout-parser"); | ||
| const { computeRowCost } = require("./pricing"); | ||
| const wsl = require("./wsl-probe"); |
There was a problem hiding this comment.
Bump all release versions for this src change
This commit changes src/ but leaves package.json, both MARKETING_VERSION entries, and the Windows <Version> at 0.87.0. The npm publishing workflow only publishes when the package version is new, and the desktop release workflow expects the synchronized versions, so this fix cannot ship through the documented release flow until npm version/sync-versions updates all four locations. The repository delegates its release requirements and conventions to CLAUDE.md.
AGENTS.md reference: AGENTS.md:L3-L5
Useful? React with 👍 / 👎.
… a silent heuristic The home-identity check that keeps injected homes hermetic is inert on the real CLI path (buildSessionAnalytics defaults home to os.homedir(), so the comparison is always true there) and fires only for a threaded home. That made it a latent trap: the whole reason to thread a non-default home is to scan a tree that is not os.homedir(), and such a caller would lose WSL discovery silently -- no error, and no test would fail. Keep the same default, but express it as a `probeWsl` dep so the decision is nameable and reachable. Two tests pin the escape hatch in both directions. Raised by an independent verification run on a second WSL host, which also confirmed the fix resolves \wsl$\Ubuntu-24.04\... with no UNC-prefix fallback needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Pushed The CI caveat, stated plainlyThe green checks on this PR do not validate the WSL code paths. GitHub runners have no WSL distro and no Independent verification on real hardwareRun on a second Windows 11 + WSL2 box, distro
Full suite there: 1878 pass / 62 fail on this branch vs 1865 / 67 on Caveat worth keeping: one paired run can't fully exclude a regression that flaked green. The change is one function with dedicated tests, so I'd call that low but not zero. What
|
xiufengsun
left a comment
There was a problem hiding this comment.
Thanks for the detailed real-hardware verification. I re-reviewed the current head 09f49a8b33747765eead8d113fbcbf7cff1c4ad5 against main, and there are two correctness issues that need to be addressed before merge.
-
Mirrored Claude sessions are double-counted across native and WSL roots.
new Set(claudeGroups.flat())only deduplicates identical path strings, so the native path and its WSL/UNC copy both survive.claudeMessageDedupKeycannot collapse them here:scanClaudeSession()deliberately scopesseenMessagesper file. Downstream,summarizeSessions()counts both rows, whilemergeSessionFragments()merges the browser row by adding their token/cost totals.I reproduced this with byte-identical native and WSL copies of one Claude session containing 12 tokens:
- expected: summary
1 session / 12 tokens, browser1 session / 12 tokens - actual: summary
2 sessions / 24 tokens, browser1 session / 24 tokens
Please add deterministic cross-root duplicate ownership for identical mirrored copies while preserving legitimate divergent resume/subagent fragments that share a session ID. The regression coverage should include both an identical-copy case (count once) and a divergent-copy case (retain the non-overlapping fragments). A simple session-ID-only drop would lose legitimate fragments.
- expected: summary
-
The generic root selector turns
wsl-firstandnative-firstintoboth, including for Codex.shouldProbeNative()andshouldProbeWsl()both return true for the two*-firstmodes, soproviderRoots()returns both roots whenever both exist. That contradicts the documented mode contract:wsl-firstselects WSL with native fallback,native-firstselects native with WSL fallback, and onlybothaggregates both.Claude sync intentionally unions allowed installs, but Codex sync still uses
resolveInstallPaths()and selects one install in the*-firstmodes. The current PR therefore makes Codex session analytics scan native + WSL while Codex token totals scan only the selected install. Please split the provider policies (Claude may keep its intentional union; Codex should follow the existing resolver semantics) and add a Codex mode matrix coveringwsl-first,native-first,both,wsl-only, andnative-only.
The PR-specific and surrounding tests are otherwise healthy. I also ran the full local suite; its two failures reproduce unchanged on the base commit and are unrelated. The green CI does not catch the two issues above because the new tests exercise root selection only and currently assert the incorrect default union, without testing cross-root mirrored Claude content.
Follow-up to the last section of #374 (comment) — the sessions pipeline scanning only the native home. Separate from the attribution fix in v0.86.1; no shared code.
Why
discoverSessionFilesbuilt its Claude and Codex roots fromos.homedir()alone.syncalready walks both Claude homes (src/commands/sync.js), so on a Windows host with a WSL install the same work is counted in the token totals but missing from the session browser, its project list, and thetracker sessionsexport.TOKENTRACKER_WSL_MODEhad no effect on any of them.That split is the confusing part in practice: the numbers look right, so nothing signals that the session-level views are showing a subset.
What
Build the roots through the existing
wsl-probehelpers (shouldProbeNative/shouldProbeWsl/discoverWslHome), the same waysync.jsdoes, so one env var gates both sides consistently. Discovery is additive; duplicates synced between environments collapse via the existing Codex session-id pass andclaudeMessageDedupKeydownstream, so no new dedup logic.WSL is probed only when
home === os.homedir().discoverWslHomeresolves\wsl$independently of whateverhomeit's called alongside, so probing on an injected home splices the machine's live WSL transcripts into what the caller expects to be an isolated fixture. I hit this on the first attempt — two suite tests started seeing 467 real sessions where they asserted 1.Verification
Windows 11 + WSL2 Ubuntu, against
main@ 0.87.0:TOKENTRACKER_WSL_MODEnative-onlybothnative-onlyis byte-identical to pre-change output — the native path is untouched.Tests:
providerRootsis made dependency-injectable (platform,homedir,discoverWslHome) and exported, so root selection is exercised without needing a WSL install on the runner — the new file passes on Linux CI. Eight cases cover mode gating, an unresolvable WSL home, non-win32, and the isolation guard. Removing the guard fails the isolation test and leaves the other seven green.Ran
session-analytics+local-api+wsl+dual-installsuites inboth/native-only/wsl-firstagainst this branch and against unpatchedmain: failure counts identical (11 / 13 / 11), pass counts +8 (the new tests). Those pre-existing failures are unrelated and environment-coupled — several suites read the real~/.claude,~/.codexand\wsl$shares rather than fixtures, so they don't reproduce on the ubuntu CI runner. Happy to file that separately if useful.Notes
resolveGrokHomehas its own override handling and I didn't want to change that behaviour blind.Summary by CodeRabbit
New Features
Bug Fixes
Tests