fix(cli): normalize empty Windows profile before imports (#2482)

Follow-up to #2345; thanks to @gefrezim for the import-time home lookup diagnosis.

Normalize only an empty Windows USERPROFILE before runtime dependency imports, preserving all nonempty caller selections. Reuse the existing public setup dry-run regression in the Windows curated CI lane. No additional filesystem query, engine call, cache or retry.

Verified real Windows Node 24.18.0 CLI unset/empty/selected-profile controls, setup dry-run home selection and unchanged configuration, and baseline/candidate SDK stdio MCP catalogue parity. Nine related tests and static/docs checks passed. No IDA or Ghidra engine was used. Scoped resource observations and measurement limits are recorded in experiments/analysis-efficiency/windows-empty-profile.md.

Fixed the review finding and original CI package-scope ownership failure. Current-head CI is green; the unrelated MCP identity timeout passed on a failed-job rerun without changing its code or timeout. Current-head review completed with no further findings.
This commit is contained in:
Ysopwnable 2026-10-13 12:43:52 +08:00 committed by GitHub
parent 45984a4319
commit fa4b828865
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 77 additions and 0 deletions

View File

@ -509,6 +509,7 @@ jobs:
tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts
tests/boundary/filesystem/setupUnavailableProvider.test.ts
tests/boundary/filesystem/windowsNpxRegistration.test.ts
tests/acceptance/setup/homeDirectoryFallback.test.ts
tests/boundary/filesystem/webScriptManifestPaths.test.ts
tests/boundary/process/ProcessEvidence.host.test.ts
tests/acceptance/webModuleCli.test.ts

View File

@ -0,0 +1,69 @@
# Windows empty-profile startup verification
Follow-up to [#2345](https://github.com/morluto/rea/pull/2345), including
@gefrezim's diagnosis of the import-time Windows home lookup.
## Source and boundary
Baseline: main `172407787f1842dedbcd27b5385177595f786ce4`. Verification used
the real Windows 11 10.0.22631.0 host, official Windows x64 Node 24.18.0,
locked production dependencies and REA's compiled public CLI / SDK 2.3.1 MCP
server. No IDA or Ghidra engine was used. Runtime copies and compact receipts
live outside the checkout; no account profile path or ambient environment is
recorded here.
The tested dispatcher SHA-256 is
`526105418604cc6e5a202a6275b700814e27450bfaffd45e7510530ba669de8d`.
Only the dispatcher changed in the deployed runtime. It treats an empty Windows
`USERPROFILE` as unset before dependency imports. Nonempty selections continue
to reach the existing home resolver; no additional lookup or fallback owner is
introduced.
## Real Windows observations
With `HOME` empty in each case, the baseline produced:
| USERPROFILE selection | `--version` | `--help` | MCP discovery |
| --------------------------- | ----------------------- | ----------------------- | ------------- |
| Unset | Exit 0 | Exit 0 | Healthy |
| Empty | Exit 1, `uv_os_homedir` | Exit 1, `uv_os_homedir` | Healthy |
| Nonempty test-owned profile | Exit 0 | Exit 0 | Healthy |
The corrected CLI passed all six invocations with no stderr. Version output
remained 6 bytes and help output 15,497 bytes. Real setup dry runs with empty
and nonempty profile selections both exited 0, planned their configuration
under the expected home, preserved existing configuration bytes and created no
configuration under the working directory. No installation was performed.
MCP was already healthy in the baseline; this change does not claim to repair
an MCP startup failure. Complete baseline discovery was 187 tools and 2,545,790
serialized bytes, SHA-256
`1e6bbca7f62852134eb703ea098f5a302d1fe333b8433475429f6fc1b1aba18f`.
All three corrected MCP controls also passed with no stderr and the same
catalogue bytes and digest.
The existing `homeDirectoryFallback` acceptance test exercises the import-time
failure through the public setup dry run. Adding it to the Windows curated lane
makes that regression execute on the affected platform. No duplicate startup
test was retained. Nine related tests passed, including the existing MCP doctor
and curated-lane path checks; Linux passing alone does not prove Windows behavior.
Review caught a missing package-scope registration for the newly listed test;
the original CI shard failed that ownership invariant. The path is now also
registered in `scripts/ci/scopes.mjs`, and all 84 scope checks plus the two
curated-path checks pass. Runtime source and its tested digest are unchanged.
## Efficiency and resource limits
Heavy host probes ran serially at BelowNormal priority, with one CPU and a
512 MiB CLI/server heap setting (256 MiB for the probe parent). Heap settings do
not bound total process memory. An exit-time `process.resourceUsage()` observer
recorded individual corrected CLI invocations: CPU time ranged from 7.968 to
9.874 seconds and peak working set from 299,956 to 301,852 KiB. These figures
exclude the probe parent and any observer overhead outside the CLI process.
The six observed wall times ranged from 8.656 to 10.961 seconds.
The baseline did not use that resource observer, and cold/warm caches were not
normalized. These observations establish functional behavior and bounded probe
scope; no CPU, memory or latency improvement is claimed. The fix adds one
constant-time environment check, no filesystem lookup, engine call, retry or
cache. Output bytes are not model tokens; actual model usage was not measured.

View File

@ -55,6 +55,7 @@ export const SCOPES = {
"tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts",
"tests/boundary/filesystem/setupUnavailableProvider.test.ts",
"tests/boundary/filesystem/windowsNpxRegistration.test.ts",
"tests/acceptance/setup/homeDirectoryFallback.test.ts",
"tests/boundary/filesystem/webScriptManifestPaths.test.ts",
"tests/boundary/process/ProcessEvidence.host.test.ts",
"tests/acceptance/webModuleCli.test.ts",

View File

@ -16,6 +16,12 @@ const isMcpMode =
const isMcpDoctorMode = args[0] === "mcp" && args[1] === "doctor";
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
// Windows home lookup throws for an empty USERPROFILE. Dependencies resolve
// home during import, before REA's own resolver can apply its account fallback.
if (process.platform === "win32" && process.env.USERPROFILE === "") {
delete process.env.USERPROFILE;
}
// Playwright's registry assumes one of its three desktop host platforms when
// it chooses the browser cache directory. Termux reports `android`, although
// its Node runtime and filesystem are POSIX-compatible. Keep the cache path