fix(windows): resolve $HOME through one helper that falls back to USERPROFILE - #18
Conversation
…RPROFILE `HOME` does not exist on native Windows; `USERPROFILE` is the equivalent. Every adapter read `HOME` directly, so on Windows each one silently fell through to its own fallback and registered its hooks — or wrote its plugin, its pastes, its ingest.env — under a path Windows has no notion of. Nothing errored; the toggle simply read on against a config no agent would ever load. Four modules (ingest, codex, opencode, antigravity) each carried a private, byte-identical `fn home() -> String`. Those collapse into one `src/home.rs`: `home()` returns `Option<String>` so every call site keeps the fallback it already had, and `home_or_tmp()` holds the `/tmp` default the four adapters share so that literal has one definition instead of four. This changes where the value comes from, not what happens when it is missing — clipboard still short-circuits with `?`, the extractor and `project_key` still fall back to an empty string, the adapters still land on `/tmp`. The two `#[cfg(unix)]`-gated `pty.rs` tests stay gated. The helper makes `HOME` portable but not those assertions: one needs `~/Library` on a case-insensitive filesystem, and the other leans on a `$HOME` boundary that Windows `canonicalize` defeats by returning a `\\?\` verbatim path only for paths that exist. Both comments now say that instead of pointing at a helper that no longer needs writing. The rest of the Windows port — `sh -c` in `hook_command()`, `$SHELL` in `pty_spawn`, `pbpaste` in clipboard.rs — is out of scope and stays tracked in docs/ROADMAP.md. Closes SuperLogicAI#7
|
Review findings (CI green, but flagging before merge — this PR's whole point is stopping silent Windows failures, so these matter): src-tauri/src/home.rs:17 — src-tauri/src/pty.rs:605 — the only tests touching Simplification worth considering: home.rs:9 hand-rolls a Minor, non-blocking:
|
Addresses review findings on PR SuperLogicAI#18: home_or_tmp()'s final fallback was hardcoded "/tmp", which doesn't exist on Windows — the exact silent-failure class that PR exists to fix. Swapped for std::env::temp_dir(). The USERPROFILE fallback itself was also untested on the windows-latest CI leg (the only tests touching home() were #[cfg(unix)]-gated). Added a test that unsets HOME and confirms the fallback, guarded by a shared ENV_LOCK since parallel tests read HOME/USERPROFILE and would otherwise race. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E9BsJXxSYH1VdKpzYBZNWf
Closes #7.
HOMEdoes not exist on native Windows — the equivalent isUSERPROFILE— so every adapter readingHOMEdirectly fell through to its own fallback there and registered hooks, wrote its plugin, or resolvedingest.envunder a path Windows has no notion of. Nothing errored; the toggle simply read "on" against a config no agent would ever load.What changed
Four modules (
ingest,codex,opencode,antigravity) each carried a private, byte-identicalfn home() -> String. Those collapse into onesrc-tauri/src/home.rs:home()returnsOptionon purpose: the helper decides where the value comes from, each call site keeps deciding what happens when it's missing, which is the split the issue asked for. AString-returning helper would have had to bake in one fallback and silently change three call sites.home_or_tmp()exists so the/tmpliteral has one definition instead of four.All ten call sites are routed through it, each preserving its exact prior failure behavior —
clipboard.rsstill short-circuits with?,extractor.rsandproject_keystill fall back to an empty string, the adapters still land on/tmp. Noenv::var("HOME")remains outsidehome.rs.The two gated
pty.rstests stay gatedThe helper makes
HOMEportable; it does not make those two assertions portable.canon_resolves_case_and_tilde_to_one_keyneeds~/Libraryto exist on a case-insensitive filesystem. Where it doesn't, both spellings fall throughcanonunchanged and compare unequal. (This is really a macOS test rather than a unix one — it would fail on Linux too. The gate is only correct because CI has no Linux leg. Left alone as out of scope, but#[cfg(target_os = "macos")]may be what you actually want.)project_key_outside_a_repo_is_the_dir_itselfwould most likely pass on Windows, for the wrong reason.canonicalizereturns a\?\verbatim path for a home that exists but leaves the nonexistent~/...case unprefixed, so the$HOMEboundary the test's own comment says it covers is never reached — the walk runs to the drive root and coincidentally returns the expected value. Ungating buys a green test that verifies nothing it claims.Both gate comments said "Phase 13 re-enables against a
home()helper". The helper now exists, so they've been rewritten to state the real blocker.Two things for a reviewer to weigh
HOMEis deliberately not filtered.env::varreturnsOk("")for a set-but-empty variable, soHOME=""still wins overUSERPROFILEand yields a relative path. A.filter(|s| !s.is_empty())is a one-line addition —opencode.rs'sXDG_CONFIG_HOMEmatch already uses that idiom — but it changes Unix behavior, which this issue explicitly scoped out. Happy to add it if you'd rather have it here than in a follow-up.docs/ROADMAP.md:553-554and:560are now stale. They describe these tests as gated pending ahome()helper and list$HOMEas an open runtime gap. Left untouched under the one-concern rule; say the word and I'll strike those rows.The
\?\verbatim-prefix mismatch is a live bug beyond the tests:project_key's$HOMEboundary check only works on Windows when both sides canonicalize successfully. That's the path half of the Windows port rather than the env-var half, so it's out of scope here — but it belongs on #8's successor if you want it tracked.Verification
Draft, because I could not compile this. I don't have Rust on the machine I wrote it on and the app is macOS-only, so neither of the issue's "done when" criteria was executed locally. What I did instead was a line-by-line type read of every call site —
StringvsOption<String>vsResult<String, _>, thepty.rs:66tuple match re-derived, imports confirmed, no crate-level lint attributes that would change clippy's behavior.I'm opening it as a draft specifically so CI's
macos-latestandwindows-latestlegs are the first thing to actually compile it. If clippy or the test suite complains I'll fix and repush. Please don't merge on my word that it builds — merge on the checks.Explicit non-goals, untouched:
hook_command()'ssh -c,pty_spawn's$SHELL,clipboard.rs'spbpaste.