Skip to content

windows: discover provider roots inside WSL distros - #1062

Open
iamtoruk wants to merge 8 commits into
mainfrom
feat/wsl-discovery
Open

windows: discover provider roots inside WSL distros#1062
iamtoruk wants to merge 8 commits into
mainfrom
feat/wsl-discovery

Conversation

@iamtoruk

Copy link
Copy Markdown
Member

Fixes #1059.

What

On Windows, CodeBurn only scanned the Windows home, so agents running inside WSL showed zero sessions. This discovers WSL distro homes and adds them as extra provider roots — V1 covers claude and codex (the two providers with an existing multi-root seam; others when reported).

  • Running distros only, by default. Touching \\wsl$\<distro> boots a stopped distro, so enumeration uses wsl.exe --list --quiet --running (absolute System32 path, 3 s timeout, UTF-16LE/UTF-8 detected by content, memoized per process). CODEBURN_WSL=all opts into stopped distros (documented as "will start them"); off never spawns wsl.exe. Read-policy env — deliberately outside PROVIDER_ENV_VARS (can only add/remove source paths; never makes a kept entry stale).
  • \\wsl$\ probed first, \\wsl.localhost\ fallback (the reverse stalls multi-seconds through MUP→SMB→DNS on builds without the newer name). Utility distros (docker-desktop, podman, rancher) and non-distro output (the localized "no installed distributions" message) filtered by a single-token shape.
  • 9P carve-out: dev/ino zeroed in fingerprints for \\wsl$ paths at all four sites (unstable over 9P would force a full re-parse every run); mtime+size matching is the existing codex-cache precedent.
  • Offline ≠ deleted: cache entries under WSL paths are exempt from missing-file eviction and still serve when the distro is stopped — a wsl --shutdown cycle no longer wipes and re-parses everything over 9P, and read-only/write runs agree.
  • Linux cwds (/home/u/proj) in those transcripts attribute safely on win32 (fix: OOM on cold parse (V8 SlicedString retention) + kiro projectPath for repo attribution #984's platform guard, test-proven both ways). Multi-home distros label as Ubuntu (WSL, alice). codeburn doctor prints one line naming why no WSL roots were probed (off, or nothing running) with the all hint.
  • No-WSL Windows cost: missing wsl.exe fails in ~0.8 ms, once per process. macOS/Linux: module inert — 31.5 MB of real exported data byte-identical to main.

Tests

36 new across tests/wsl-discovery.test.ts + tests/wsl-spawn.test.ts: UTF-16 (±BOM)/CRLF/UTF-8 parsing, no-distro message yields zero probes, utility filter, env modes (off never calls execFileSync), prefix fallback + per-distro break, multi-home labels, probeRoots/doctor, /home cwd on win32 (discriminates: fails without the guard), fingerprint carve-out + reconciliation, WSL-offline retention vs normal-path eviction (fails with the carve-out stubbed off).

tsc clean · npm test 2996 · test:locks 26.

Needs a real Windows + WSL box (verification checklist)

codeburn doctor                      # \\wsl$\...\.claude\projects rows, or the one-line reason
codeburn status --format json        # WSL sessions counted
$env:CODEBURN_WSL="off"; codeburn doctor   # opt-out named; wsl.exe never spawned
Measure-Command { codeburn status }  # 2nd run fast (9P fingerprint carve-out)
wsl --shutdown; codeburn status      # spend still shown; restart; still no full re-parse

@Mateleo — would you be up for running this checklist once it merges (or from the branch)? Your report is the real-world validation this needs.

Follow-up filed: #1061 (Windows menubar quota panel reads only the Windows home).

Agents run inside a WSL distro write their history to the distro's Linux
home, exposed to Windows as \\wsl$\<distro>\... and never under the user
profile, so a Windows-only scan reported zero sessions for them.

src/wsl.ts lists distros through %SystemRoot%\System32\wsl.exe (absolute,
so nothing dropped next to the CLI can impersonate it) with a 3s timeout,
decodes its UTF-16LE output, drops container-runtime distros, and
enumerates each distro's home/* and root. Running distros only by
default: touching \\wsl$ boots a stopped distro. CODEBURN_WSL=all|off
opts in or out. Inert off win32 and memoized per process.

claude and codex append <wslHome>/.claude and <wslHome>/.codex to their
existing root lists, so multi-root discovery, probeRoots() and doctor
cover them unchanged. Roots are additive, never an override.

CODEBURN_WSL stays out of PROVIDER_ENV_VARS: cache entries are keyed by
source path, so it can only add or drop paths, never make a kept entry
stale, while declaring it would force a re-parse on every toggle.

Fingerprints drop dev/ino for \\wsl$ paths in both caches. The 9P share
synthesizes them per mount, so keying on them would re-parse every WSL
session on every run; mtime+size still catches edits and appends.

Refs #1059
UTF-16 output parsing, utility-distro filtering, CODEBURN_WSL modes, UNC
classification, claude/codex root injection and discovery, probeRoots,
fingerprint reconciliation with zeroed dev/ino, and the win32 guard that
keeps a Linux cwd from being walked on Windows.

Refs #1059
Probe \\wsl$\ before \\wsl.localhost\. The older spelling works on every
WSL build; probing the newer one first sends builds without it through
MUP, SMB and DNS resolution for a host named "wsl.localhost" — seconds
of stall per distro, every process.

Only accept lines shaped like a distro name. With nothing installed,
wsl.exe prints prose on stdout whose lines otherwise read as distro
names and get probed over UNC. A name must be a single token with none
of the characters Windows bans from a path component and no trailing
period, which discards that message in any UI language.

Do not evict a \\wsl$ cache entry for being undiscovered, and serve it
in write mode too. A stopped distro drops its whole root out of
discovery; treating that as a deletion re-parsed everything over 9P on
the next start and made totals drop while the read-only serve path still
showed them. The exemption is scoped to isWslUncPath — an ordinary path
whose file is gone is still evicted.

Also: getCachedCodexProject was the one fingerprint site still keying on
dev/ino for WSL paths; doctor now prints why it probed no WSL roots; and
a distro contributing several homes labels them per user rather than
letting makeUniqueLabels number them.

Refs #1059
@iamtoruk

Copy link
Copy Markdown
Member Author

@Mateleo — before we merge this, would you test it from the branch and report back? You're the real-world case (#1059) and none of us has a Windows + WSL box.

Setup (PowerShell, in a scratch dir):

git clone --depth 1 --branch feat/wsl-discovery https://github.com/getagentseal/codeburn.git
cd codeburn
npm ci
npm run build:cli

Then, using the fresh build (node dist/cli.js instead of your installed codeburn, so your normal install stays untouched):

wsl.exe --list --quiet --running          # sanity: what's running
node dist/cli.js doctor                    # expect \\wsl$\<distro>\home\<you>\.claude\projects rows — or a one-line note saying why none
node dist/cli.js status --format json      # your WSL sessions should now be counted
$env:CODEBURN_WSL="off"; node dist/cli.js doctor   # the note should name the opt-out; wsl.exe must not run
Remove-Item Env:CODEBURN_WSL
Measure-Command { node dist/cli.js status }        # run this line twice — the 2nd should be much faster
wsl --shutdown; node dist/cli.js status            # your WSL spend should STILL show while the distro is stopped
# restart your distro (open a WSL terminal), then:
Measure-Command { node dist/cli.js status }        # should still be fast — no full re-scan after the shutdown cycle
node dist/cli.js sessions --provider codex         # if you use Codex in WSL: projectPath should show /home/... paths

What we'd love in your conclusion:

  1. Did your WSL sessions appear, and do the totals look right?
  2. Rough wall-clock of the first vs second status (tells us if the network-filesystem handling works).
  3. Anything odd in doctor's WSL rows (wrong distro, missing home, duplicates).
  4. Your distro + Windows version (wsl.exe --version).

Everything is read-only — it never writes into WSL. Thanks for filing this; your report decides the merge.

@ozymandiashh ozymandiashh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking issue: wslHomes() memoizes the running-distro homes for the lifetime of the process. In long-lived serve/menubar use, a distro cached as running can be shut down and the next refresh still touches its \\wsl$ UNC path—the operation this PR explicitly avoids because it may start the stopped distro. The inverse is also stale: a distro started after an initial empty discovery is invisible until process restart. Please make discovery refresh/revalidate the currently running distro list before any UNC access (a bounded TTL or per-scan refresh would work) and cover both shutdown and later-start transitions. The requested real Windows + WSL validation is also still outstanding.

@Mateleo

Mateleo commented Aug 24, 2026

Copy link
Copy Markdown

Tell me if @ozymandiashh is indeed blocking, and if not I will test

# Conflicts:
#	src/doctor.ts
#	src/parser.ts
#	src/providers/claude.ts
#	src/providers/codex.ts
#	src/session-cache.ts
wslHomes() memoized the running-distro homes for the process lifetime. In
long-lived serve/menubar use a distro cached as running could be shut down
and the next refresh would still touch its \\wsl$ UNC path — which can hang
win32 fs calls — while a distro started after an empty discovery stayed
invisible until restart.

Discovery now caches for a 60s TTL only; on expiry the wsl.exe -l --running
probe (timeout-bounded) re-validates the running list before any UNC path
is touched, and a failed probe fails closed to native-only discovery rather
than serving stale homes. Covers both shutdown and later-start transitions.

Addresses the blocking review finding on PR #1062.
@iamtoruk

Copy link
Copy Markdown
Member Author

@Mateleo — yes, @ozymandiashh's finding was blocking and is now fixed, plus the branch is freshly rebased onto current main (it had drifted behind the perf wave). What changed:

  • wslHomes() no longer caches running-distro state for the process lifetime: 60s TTL, and on expiry the timeout-bounded wsl.exe -l --running probe re-validates BEFORE any \\wsl$ path is touched. Probe failure fails closed to native-only discovery — a stopped distro's UNC path is never dereferenced (the hang hazard from the review).
  • New tests cover shutdown mid-session (stale home dropped, no UNC touch), later distro start (picked up on next TTL), fail-closed on probe error.

@ozymandiashh — would value your re-review of src/wsl.ts in 3eba8d5e.

@Mateleo — the branch is ready for your real-world test now; same setup steps as my earlier comment, from the updated feat/wsl-discovery. Your WSL box is the only real validation this can get — nothing here runs Windows. Once your test passes and the re-review clears, this merges (targeting 0.9.22; 0.9.21 is closing now).

@ozymandiashh

Copy link
Copy Markdown
Collaborator

Follow-up pushed in 8ffd1a2 after merging current main in e4421b9 (including #999). I re-read the full thread and all six prior commits first; the author head was still 3eba8d5, so this is a normal fast-forward of feat/wsl-discovery, not a force-push.

Red-before reproductions

  1. Active WSL home + actually deleted transcript: the cached Claude/Codex project was still reported forever because every undiscovered \\wsl$ path was treated as an offline distro.
  2. WSL-only Codex cache + zero newly discovered sources: the result was []; the generic orphan pass never ran for a non-durable provider unless another source happened to be present.
  3. Raw Codex result-cache flush: it statted every cached UNC path. A stopped distro could hang the refresh or be mistaken for deletion. Simply skipping every WSL path then left a second edge case: a confirmed active-root deletion stayed in the raw cache and could be reused if that exact path was later recreated with identical mtime/size.
  4. Long-lived mode changes: a cached running result could survive a switch to all/off for the TTL window.

What changed

  • WSL homes now have a 60-second, mode-aware TTL; running/all changes re-probe immediately and off immediately disables discovery and UNC access.
  • Orphan reconciliation refreshes the running-home probe before classifying a missing WSL source:
    • active home + missing ordinary transcript => real deletion, evicted;
    • offline home => cached usage retained and reported without touching UNC;
    • CODEBURN_WSL=off => no WSL discovery/UNC I/O, while historical cached totals remain reportable (the opt-out is a read policy, not a destructive cache purge).
  • WSL-only cached Codex usage is processed even when there are zero discovered Codex sources.
  • flushCodexCache() never stats WSL UNC paths. When parser reconciliation proves an exact Codex path was deleted under an active home, it explicitly removes and persists that exact raw-cache row, including the zero-source case.
  • \\wsl$ and \\wsl.localhost are canonicalized for classification; distro/share matching is case-insensitive while the Linux suffix remains case-sensitive.
  • Durable-provider WSL orphans preserve the existing monotonic-history semantics. Claude PR-linked orphan rows also retain their existing attribution exception; docs now say so.

Verification

  • Focused WSL/cache regressions: 6 files, 56 tests passed.
  • npm test: 250 files passed, 3,362 tests passed, 5 declared skips.
  • npm run test:locks: 4 files, 34 tests passed.
  • npm run build:cli: passed.
  • npx tsc --noEmit: passed.
  • git diff --check: passed.
  • Two independent final blocker reviews of the completed diff: no remaining code blocker found.

@Mateleo, the real Windows + WSL run is still the external merge gate; none of the local/CI tests can replace it. Please test the latest feat/wsl-discovery head (8ffd1a2). In particular, after wsl --shutdown, historical totals should remain visible without UNC access; after restarting the distro, deleting a disposable transcript under an active home should make it disappear on the next write refresh. Please do not delete real history for the test.

I am intentionally leaving the existing requested-changes gate in place until the new CI and that real Windows + WSL validation are both back.

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.

Codeburn should see WSL

3 participants