Skip to content

fix(windows): resolve $HOME through one helper that falls back to USERPROFILE - #18

Merged
SuperLogicAI merged 2 commits into
SuperLogicAI:mainfrom
zeiddata-dev:fix/windows-home-helper
Sep 6, 2026
Merged

fix(windows): resolve $HOME through one helper that falls back to USERPROFILE#18
SuperLogicAI merged 2 commits into
SuperLogicAI:mainfrom
zeiddata-dev:fix/windows-home-helper

Conversation

@zeiddata-dev

Copy link
Copy Markdown
Contributor

Closes #7.

HOME does not exist on native Windows — the equivalent is USERPROFILE — so every adapter reading HOME directly fell through to its own fallback there and registered hooks, wrote its plugin, or resolved 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.

What changed

Four modules (ingest, codex, opencode, antigravity) each carried a private, byte-identical fn home() -> String. Those collapse into one src-tauri/src/home.rs:

pub fn home() -> Option<String>   // HOME, then USERPROFILE
pub fn home_or_tmp() -> String    // the /tmp default the four adapters share

home() returns Option on 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. A String-returning helper would have had to bake in one fallback and silently change three call sites. home_or_tmp() exists so the /tmp literal has one definition instead of four.

All ten call sites are routed through it, each preserving its exact prior failure behavior — clipboard.rs still short-circuits with ?, extractor.rs and project_key still fall back to an empty string, the adapters still land on /tmp. No env::var("HOME") remains outside home.rs.

The two gated pty.rs tests stay gated

The helper makes HOME portable; it does not make those two assertions portable.

  • canon_resolves_case_and_tilde_to_one_key needs ~/Library to exist on a case-insensitive filesystem. Where it doesn't, both spellings fall through canon unchanged 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_itself would most likely pass on Windows, for the wrong reason. canonicalize returns a \?\ verbatim path for a home that exists but leaves the nonexistent ~/... case unprefixed, so the $HOME boundary 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

  1. Empty-string HOME is deliberately not filtered. env::var returns Ok("") for a set-but-empty variable, so HOME="" still wins over USERPROFILE and yields a relative path. A .filter(|s| !s.is_empty()) is a one-line addition — opencode.rs's XDG_CONFIG_HOME match 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.
  2. docs/ROADMAP.md:553-554 and :560 are now stale. They describe these tests as gated pending a home() helper and list $HOME as 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 $HOME boundary 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 — String vs Option<String> vs Result<String, _>, the pty.rs:66 tuple 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-latest and windows-latest legs 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()'s sh -c, pty_spawn's $SHELL, clipboard.rs's pbpaste.

…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
@SuperLogicAI

Copy link
Copy Markdown
Owner

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:17home_or_tmp()'s final fallback is hardcoded "/tmp", which doesn't exist on Windows. If neither HOME nor USERPROFILE is set there, hook config resolves under an uncreatable path and install fails silently — the exact failure mode this PR exists to fix.

src-tauri/src/pty.rs:605 — the only tests touching home::home() are #[cfg(unix)]-gated, so the new USERPROFILE fallback path is never exercised by cargo test, even on the windows-latest CI leg. A future regression there ships green.

Simplification worth considering: home.rs:9 hand-rolls a HOMEUSERPROFILE fallback that duplicates std::env::home_dir(), which is no longer deprecated and resolves via the Windows profile API (more robust than trusting a possibly-stale USERPROFILE). Confirmed it compiles clean, undeprecated, on this toolchain (rustc 1.97.0).

Minor, non-blocking:

  • clipboard.rs:23 — now does an unnecessary create_dir_all on Windows before failing later at the macOS-only osascript spawn (harmless, just wasted I/O).
  • extractor.rs:5 — joins a Unix-style literal path ("{}/.local/bin/claude") instead of PathBuf::join; harmless today but fragile groundwork for future Windows-path work in that function.

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
@SuperLogicAI
SuperLogicAI marked this pull request as ready for review September 6, 2026 21:37
@SuperLogicAI
SuperLogicAI merged commit 5edad6d into SuperLogicAI:main Sep 6, 2026
3 checks passed
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.

Windows: $HOME is unset — add a home() helper and route every call site through it

2 participants