Skip to content

test(windows): bring the Windows CI suite to green (299→0), no Linux regression#278

Merged
khustup2 merged 10 commits into
mainfrom
fix/windows-ci-green
Jun 19, 2026
Merged

test(windows): bring the Windows CI suite to green (299→0), no Linux regression#278
khustup2 merged 10 commits into
mainfrom
fix/windows-ci-green

Conversation

@khustup2

@khustup2 khustup2 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Make the Windows CI suite green (without regressing Linux/macOS)

The Typecheck and Test (Windows) job had been red on every main push since it was added — 299 failing tests across 53 files. This brings it to green, with the Linux test job verified green at every step.

Result

  • Windows full suite: 299 → 0 failures (240 files / 4502 tests pass, 107 platform-skipped).
  • Linux/macOS: no regression — every change verified behavior-neutral; coverage stays enforced + reported on the Linux job.
  • Proven green end-to-end on this branch (CI run on b37fcec7, all jobs pass).

Root causes & fixes

1. os.homedir() ignored faked $HOME on Windows (the big one — hundreds of failures). It reads %USERPROFILE% there, not $HOME. New tests/shared/fake-home.ts (setFakeHome/clearFakeHome) sets all four home vars; adopted across 27 test files. Behavior-neutral on POSIX.

2. Real cross-platform product bugs (fixed, benefit actual Windows users):

  • notifications/{queue,state}.ts: home-containment used startsWith(home + "/") — rejected every write on Windows (resolve() emits \). Now path.relative-based, extracted with the atomic-rename into shared src/utils/atomic-write.ts.
  • atomic-write.ts: renameSync retries on Windows EPERM/EBUSY/EACCES (transiently-open dest) — fixes cross-process queue/state/summary-state writes. Wired into summary-state.ts too. Removes prior duplication.
  • notifications/state.ts: dropped : from the claim-file sanitizer (illegal in Windows filenames → tryClaim failed open every call).
  • skillify.ts: --to project dest used a hardcoded /.claude/skillspath.join.
  • install-hermes.ts, spawn-mine-local-worker.ts, autoupdate.ts: separator normalization / where vs which / path.delimiter + .cmd/.exe probing.
  • scripts/{sync-versions,pack-check}.mjs: CRLF-shebang import break (+ .gitattributes eol=lf); npm spawn via shell on Windows (.cmd EINVAL).

3. Test portability: path assertions rebuilt with path.join/basename/isAbsolute; subprocess env carries USERPROFILE; inline-TS children run via a temp file + node --import tsx (process.execPath, no shell — npx tsx -e mangled the code under cmd.exe); process.cwd() vs 8.3 short-path fixes.

4. POSIX-only suites skipped on Windows (with comments): Unix-socket embed daemon, /bin/sh safe-echo, Unix file-mode bits, and a couple of fs-mode-injection paths.

5. CI: the windows-test job no longer runs --coverage — it exists to catch Windows-only failures, and the platform-skips above make per-file thresholds unsatisfiable there by design. Coverage remains fully enforced + reported on the Linux job (new code is covered: atomic-write.ts 100/95/100/100, runGate coverage restored via POSIX fixtures).

Notes

  • New coverage thresholds added for atomic-write.ts; runGate spawn coverage restored under skipIf(win32).
  • 9 src files, 56 test files. The temporary on.push.branches trigger used to drive the push-only Windows job during development has been reverted.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed cross-platform path handling for Windows compatibility (path separators, line endings)
    • Improved binary discovery for Windows environments
    • Enhanced file operation robustness with atomic writes and retry logic
    • Resolved Windows-specific socket and executable execution issues
  • Chores

    • Refactored test infrastructure for better cross-platform coverage
    • Updated CI/CD workflows for Windows test stability

khustup2 added 10 commits June 19, 2026 01:03
Temporary trigger so the Windows full-suite job (push-only) runs on this
branch while we fix it. Revert before merge.
…dir honors temp HOME

os.homedir() reads %USERPROFILE% (then %HOMEDRIVE%%HOMEPATH%) on Windows, not
$HOME. 27 test files faked the home dir via process.env.HOME only, so on
Windows production code resolved the real C:\Users\<runner>\... — causing
hundreds of path-assertion + ENOENT failures.

Add tests/shared/fake-home.ts (setFakeHome/clearFakeHome) setting all four
vars, and adopt it across the 27 HOME-faking files. Behavior-neutral on
POSIX (homedir reads $HOME), so Linux/macOS results are unchanged.
Fix the cross-platform bugs and skip genuinely-POSIX-only tests so the
Windows full suite passes; all changes behavior-neutral on Linux/macOS.

Product (real cross-platform bugs):
- notifications/{queue,state}.ts: home-containment used startsWith(home+'/')
  (rejected every write on Windows where resolve() emits '\') -> path.relative;
  add renameAtomic with a Windows EPERM/EBUSY/EACCES retry for the temp->dest
  swap.
- cli/install-hermes.ts: isHivemindHook now normalizes '\'->'/' so re-install
  dedup matches on Windows (was duplicating hooks).
- skillify/spawn-mine-local-worker.ts: findHivemindLauncher uses 'where' on
  win32 (was hardcoded 'which').
- skillify/gate-runner.ts: export buildArgs for deterministic argv assertions.
- scripts/sync-versions.mjs: drop the shebang (only run via 'node'); it broke
  vitest import on a CRLF checkout. Add .gitattributes eol=lf as the backstop.

Tests:
- Path-separator assertions rebuilt with path.join/basename/isAbsolute and
  separator-normalized comparisons (index-marker-store, graph/*, cli-*,
  skillify-*, config, pre-tool-use, plugin-cache, summary-state, ...).
- Subprocess env stubs now set USERPROFILE (+ file:// href) so os.homedir
  resolves the temp home on Windows.
- skipIf(win32) for genuinely POSIX-only tests (embed-daemon unix-socket IPC,
  safe-echo /bin/bash, Unix file-mode bits, 2 .mjs-direct-spawn stage-memory
  tests already covered cross-platform by stage-memory-shell-spawn).
…erage

Product (real cross-platform fixes):
- Extract shared src/utils/atomic-write.ts (renameAtomic + isPathInsideHome)
  from the duplicated copies in notifications/{queue,state}.ts; wire both +
  hooks/summary-state.ts to it. renameAtomic retries the Windows-only
  EPERM/EBUSY/EACCES rename, fixing the cross-process producer/bump/lock
  tests (children no longer exit 1 on a transiently-open dest). Removes the
  duplication (jscpd) too.
- notifications/state.ts: drop ':' from the claim-file id sanitizer — it's a
  valid POSIX filename char but illegal on Windows, so tryClaim fric'd
  fail-open every call.
- hooks/shared/autoupdate.ts: findHivemindOnPath splits PATH on
  path.delimiter and probes .cmd/.exe on win32 (was ':'-split + bare name).

Tests:
- notifications.test.ts cross-process + bundle subprocesses: import via a
  file:// .href URL (not /C:/… pathname), pass USERPROFILE to the child, and
  shell-spawn npx on win32.
- atomic-write.test.ts: full unit coverage of renameAtomic (retry/throw/
  exhaust/default seams) + isPathInsideHome edge cases.
- skillify-gate-runner.test.ts: restore runGate spawn coverage via POSIX
  shebang fixtures (skipIf win32) — the buildArgs argv contract stays
  cross-platform.
- Straggler fixes (Windows): autoupdate PATH probe, index-marker path
  assertion, skillify-cli EBUSY teardown (chdir-out + rmSync retries),
  skillify-manifest mode-bits assertion guard, plugin-cache + cli-embeddings
  skipIf(win32) for fs-mode / unix-socket-only paths.
- vitest.config.ts: add atomic-write.ts threshold (90/90/90/90).
- notifications + summary-state cross-process tests: run the inline child via
  a temp .mts file + 'node --import tsx' (process.execPath, absolute, no
  shell) instead of 'npx tsx -e <code>'. On Windows npx needs a shell and
  cmd.exe mangled the multi-line code arg ('Transform failed'); the file +
  execPath form is arg-mangle-proof and shell-free on every platform.
- skillify-cli --to project: build the expected destination from
  process.cwd() (what the product uses) rather than the mkdtemp dir, which
  can differ from cwd on Windows (8.3 short path vs long form).
…rators)

The '--to project' destination hardcoded `${process.cwd()}/.claude/skills`,
producing mixed separators on Windows (C:\...\Temp\dir/.claude/skills) and
diverging from the 'global' branch's join(). Use join() so the path uses the
native separator. Identical output on POSIX.
All Windows tests pass, but per-file coverage thresholds were failing the
job: suites that are platform-skipped on Windows (skipIf(win32) for the
Unix-socket embed daemon, /bin/sh safe-echo, Unix file-mode bits, gate-runner
spawn, plugin-cache fs-mode injection) can't cover their src on Windows by
design. Coverage is already enforced + reported on the Linux 'test' job; the
Windows job's purpose is catching Windows-only test failures. Drop --coverage
there so platform skips don't read as coverage regressions.
scripts/pack-check.mjs ran execFileSync('npm', ...) which can't launch the
npm.cmd shim on Windows (ENOENT) — failing the Windows job's Pack-check step
once the test step started passing. Use the platform-correct binary name.
execFileSync can't launch the npm.cmd shim directly on modern Node (EINVAL).
Route through the shell on Windows; the npm pack args are static (no
injection surface). Unix stays shell-free.
Drops the branch from on.push.branches that was added to drive the push-only
windows-test job during development. The job is back to running on main/dev
push only. Windows green was verified on this branch at b37fcec (all jobs
pass).
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 27d41478-eb6f-4d00-9ed0-ddcecb1e8222

📥 Commits

Reviewing files that changed from the base of the PR and between 3dc256a and e9fb56d.

📒 Files selected for processing (70)
  • .gitattributes
  • .github/workflows/ci.yaml
  • scripts/pack-check.mjs
  • scripts/sync-versions.mjs
  • src/cli/install-hermes.ts
  • src/commands/skillify.ts
  • src/hooks/shared/autoupdate.ts
  • src/hooks/summary-state.ts
  • src/notifications/queue.ts
  • src/notifications/state.ts
  • src/skillify/gate-runner.ts
  • src/skillify/spawn-mine-local-worker.ts
  • src/utils/atomic-write.ts
  • tests/claude-code/auth-creds.test.ts
  • tests/claude-code/autoupdate.test.ts
  • tests/claude-code/config.test.ts
  • tests/claude-code/dashboard-command.test.ts
  • tests/claude-code/dashboard-data.test.ts
  • tests/claude-code/embeddings-client.test.ts
  • tests/claude-code/embeddings-daemon.test.ts
  • tests/claude-code/index-marker-store.test.ts
  • tests/claude-code/install-id.test.ts
  • tests/claude-code/install-scan.test.ts
  • tests/claude-code/notifications-coverage.test.ts
  • tests/claude-code/notifications-org-stats-source.test.ts
  • tests/claude-code/notifications-primary-banner.test.ts
  • tests/claude-code/notifications-queue-lock.test.ts
  • tests/claude-code/notifications-referral-invite.test.ts
  • tests/claude-code/notifications-usage-tracker.test.ts
  • tests/claude-code/notifications.test.ts
  • tests/claude-code/plugin-cache-gc-bundle.integration.test.ts
  • tests/claude-code/plugin-cache.test.ts
  • tests/claude-code/pre-tool-use.test.ts
  • tests/claude-code/safe-echo.test.ts
  • tests/claude-code/session-notifications-hook.test.ts
  • tests/claude-code/skillify-auto-pull.test.ts
  • tests/claude-code/skillify-cli.test.ts
  • tests/claude-code/skillify-existing-skills.test.ts
  • tests/claude-code/skillify-gate-runner.test.ts
  • tests/claude-code/skillify-legacy-migration.test.ts
  • tests/claude-code/skillify-manifest.test.ts
  • tests/claude-code/skillify-pull.test.ts
  • tests/claude-code/skillify-skill-writer.test.ts
  • tests/claude-code/skillify-unpull.test.ts
  • tests/claude-code/spawn-mine-local-worker.test.ts
  • tests/claude-code/spawn-wiki-worker.test.ts
  • tests/claude-code/stage-memory.test.ts
  • tests/claude-code/summary-state.test.ts
  • tests/claude-code/wiki-next-steps-contract.test.ts
  • tests/cli/cli-embeddings.test.ts
  • tests/cli/cli-install-claude.test.ts
  • tests/cli/cli-install-codex-fs.test.ts
  • tests/cli/cli-install-cursor-fs.test.ts
  • tests/cli/cli-install-hermes.test.ts
  • tests/cli/cli-install-mcp-shared.test.ts
  • tests/cli/cli-install-openclaw.test.ts
  • tests/cli/cli-install-pi-fs.test.ts
  • tests/cli/cli-update.test.ts
  • tests/cli/cli-util.test.ts
  • tests/cli/install-end-to-end.test.ts
  • tests/shared/atomic-write.test.ts
  • tests/shared/deeplake-api-balance-exhausted.test.ts
  • tests/shared/fake-home.ts
  • tests/shared/graph/cache.test.ts
  • tests/shared/graph/git-hook-install.test.ts
  • tests/shared/graph/snapshot.test.ts
  • tests/shared/graph/spawn-pull-worker.test.ts
  • tests/shared/plugin-state.test.ts
  • tests/shared/standalone-embed-client.test.ts
  • vitest.config.ts

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Walkthrough

Adds Windows CI support by enforcing LF line endings via .gitattributes, removing coverage from the Windows CI job, introducing a new src/utils/atomic-write.ts module with retry-on-Windows-error rename logic and a path containment check, normalizing path separators and binary lookups across production source files, creating a tests/shared/fake-home.ts helper for cross-platform HOME faking, and migrating ~30 test suites to that helper while adding Unix-only skip guards.

Changes

Windows Compatibility

Layer / File(s) Summary
CI/tooling config: LF enforcement and Windows script fixes
.gitattributes, .github/workflows/ci.yaml, scripts/pack-check.mjs, scripts/sync-versions.mjs
Adds LF-enforcement rules for .mjs/.ts/.js/.json files, removes --coverage from the Windows CI job, adds shell: isWin to pack-check.mjs for npm shim compatibility, and removes the shebang from sync-versions.mjs to avoid CRLF tokenization.
New atomic-write.ts utility + tests + coverage threshold
src/utils/atomic-write.ts, tests/shared/atomic-write.test.ts, vitest.config.ts
Introduces isPathInsideHome (path containment using path.relative/path.isAbsolute), RenameAtomicOptions (injectable rename/cleanup/backoff seams), and renameAtomic (EPERM/EBUSY/EACCES retry loop). Unit tests cover success, retryable/non-retryable errors, max-attempt exhaustion, and default backoff. Sets 90% coverage thresholds.
Production source: path separator and binary lookup fixes
src/cli/install-hermes.ts, src/hooks/shared/autoupdate.ts, src/skillify/spawn-mine-local-worker.ts, src/skillify/gate-runner.ts, src/commands/skillify.ts
Normalizes backslashes in isHivemindHook, splits PATH with path.delimiter and checks platform-specific binary candidates in findHivemindOnPath, uses where/which per platform in findHivemindLauncher, switches dest paths to path.join, and exports buildArgs.
Adopt renameAtomic/isPathInsideHome in notifications and summary-state
src/hooks/summary-state.ts, src/notifications/queue.ts, src/notifications/state.ts
Replaces all renameSync finalizations with renameAtomic. Delegates sandbox checks to isPathInsideHome. Sanitizes : from claimPathFor filenames.
fake-home.ts test utility and bulk migration
tests/shared/fake-home.ts, tests/claude-code/*, tests/cli/*, tests/shared/...
Introduces setFakeHome/clearFakeHome that mutate HOME, USERPROFILE, HOMEDRIVE, and HOMEPATH consistently across POSIX and Windows. Migrates ~30 test suites from direct process.env.HOME mutation to these helpers.
Unix-only test skip guards
tests/claude-code/embeddings-*.test.ts, tests/claude-code/safe-echo.test.ts, tests/claude-code/stage-memory.test.ts, tests/claude-code/plugin-cache.test.ts, tests/shared/standalone-embed-client.test.ts, tests/shared/graph/git-hook-install.test.ts, tests/cli/cli-embeddings.test.ts
Wraps Unix-domain-socket, POSIX-mode, shell-verbatim, and SIGTERM tests with skipIf(process.platform === "win32") or itNix guards, with explanatory comments.
Cross-platform path assertions and subprocess harness fixes
tests/claude-code/notifications.test.ts, tests/claude-code/summary-state.test.ts, tests/claude-code/skillify-gate-runner.test.ts, tests/claude-code/spawn-wiki-worker.test.ts, tests/claude-code/index-marker-store.test.ts, tests/claude-code/pre-tool-use.test.ts, tests/shared/graph/*
Replaces hardcoded POSIX path literals with path.join(). Rewrites cross-process test harnesses to write temp .mts files and spawn node --import tsx instead of npx tsx -e. Uses pathToFileURL and dynamic which/where. Normalizes CRLF before parsing. Refactors gate-runner tests to assert buildArgs directly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • activeloopai/hivemind#98: Introduced src/skillify/gate-runner.ts; this PR exports buildArgs from that same module and adds platform-aware tests against it.
  • activeloopai/hivemind#128: Modified src/notifications/state.ts claim path and atomic persistence logic; this PR further changes claimPathFor filename sanitization and adopts renameAtomic/isPathInsideHome in that file.
  • activeloopai/hivemind#232: Updated the ## Next Steps section content and contract assertions in wiki-next-steps-contract.test.ts; this PR adds CRLF normalization to that same test's parsing logic.

Suggested reviewers

  • efenocchi
  • kaghni

Poem

🐇 Hop, hop across the Windows floor,
Where backslashes lurk behind every door.
We swapped renameSync for a retry loop,
And faked our HOME without falling in a loop.
Now CI runs green on every OS—
This rabbit's cross-platform, nothing less! 🌟

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/windows-ci-green

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@khustup2 khustup2 merged commit 79e20e6 into main Jun 19, 2026
8 of 9 checks passed
@khustup2 khustup2 deleted the fix/windows-ci-green branch June 19, 2026 02:47
@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report

Scope: files changed in this PR. Enforced threshold: 90% per metric (per file via vitest.config.ts).

Status Category Percentage Covered / Total
🟢 Lines 92.27% (🎯 90%) 609 / 660
🟢 Statements 91.58% (🎯 90%) 707 / 772
🟢 Functions 97.09% (🎯 90%) 100 / 103
🔴 Branches 83.95% (🎯 90%) 429 / 511
File Coverage — 9 files changed
File Stmts Branches Functions Lines
src/cli/install-hermes.ts 🟢 91.3% 🔴 80.0% 🟢 100.0% 🟢 96.6%
src/commands/skillify.ts 🔴 89.1% 🔴 81.6% 🟢 95.7% 🔴 86.8%
src/hooks/shared/autoupdate.ts 🟢 100.0% 🔴 80.0% 🔴 80.0% 🟢 100.0%
src/hooks/summary-state.ts 🔴 83.8% 🔴 87.2% 🟢 96.4% 🔴 85.4%
src/notifications/queue.ts 🟢 97.1% 🔴 77.3% 🟢 100.0% 🟢 98.3%
src/notifications/state.ts 🟢 98.2% 🔴 86.7% 🟢 100.0% 🟢 98.1%
src/skillify/gate-runner.ts 🟢 96.4% 🔴 82.0% 🟢 100.0% 🟢 100.0%
src/skillify/spawn-mine-local-worker.ts 🟢 98.5% 🟢 95.8% 🟢 100.0% 🟢 98.2%
src/utils/atomic-write.ts 🟢 100.0% 🟢 100.0% 🟢 100.0% 🟢 100.0%

Generated for commit 2872864.

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.

1 participant