Skip to content

fix(tests): fix Windows cost-tracker HOME env override#700

Closed
chris-yyau wants to merge 3 commits intoaffaan-m:mainfrom
chris-yyau:fix/windows-cost-tracker-home
Closed

fix(tests): fix Windows cost-tracker HOME env override#700
chris-yyau wants to merge 3 commits intoaffaan-m:mainfrom
chris-yyau:fix/windows-cost-tracker-home

Conversation

@chris-yyau
Copy link
Copy Markdown
Contributor

@chris-yyau chris-yyau commented Mar 20, 2026

Summary

  • Fix cost-tracker test failures on Windows by mirroring HOME to USERPROFILE in env overrides
  • On Windows, os.homedir() checks USERPROFILE (not HOME), so tests that override HOME to redirect metrics to a temp directory fail because the script writes to the real home directory

Changes

tests/hooks/cost-tracker.test.js: In runScript(), when HOME is set in env overrides, automatically set USERPROFILE to the same value so os.homedir() resolves correctly on Windows.

Test plan

  • All 5 cost-tracker tests pass locally
  • Codex review passed
  • Windows CI should now pass for cost-tracker tests

🤖 Generated with Claude Code


Summary by cubic

Fix Windows cost-tracker test failures by mirroring HOME to USERPROFILE so os.homedir() points to the temp home on Windows. Also harden tests by building the env immutably and using try/finally for temp dir cleanup.

  • Bug Fixes
    • Use the in operator for env checks and redirect the pass-through test to a temp home to avoid writes to the real home.

Written for commit 53d760a. Summary will update on new commits.

Summary by CodeRabbit

  • Tests

    • Improved test environment handling to better mirror Windows env variables and added per-test temporary HOME directories with guaranteed cleanup for more reliable cross-platform test runs.
  • Chores

    • Standardized and simplified test code style and structure for clarity and maintainability.

On Windows, Node.js os.homedir() checks USERPROFILE (not HOME), so
tests that override HOME to redirect metrics to a temp directory fail
because the cost-tracker script writes to the real home directory.

Mirror HOME to USERPROFILE in runScript() env overrides so os.homedir()
resolves to the expected temp directory on all platforms.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Copilot AI review requested due to automatic review settings March 20, 2026 09:22
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

Refactors runScript in tests/hooks/cost-tracker.test.js to precompute a merged env from process.env and envOverrides, mirror HOME to USERPROFILE when appropriate, update tests to pass per-test temporary HOME, and add per-test cleanup and minor test-styling changes.

Changes

Cohort / File(s) Summary
Test helper & tests
tests/hooks/cost-tracker.test.js
Precompute env = { ...process.env, ...envOverrides } and pass it to spawnSync (removed inline env spread). If envOverrides provides HOME but not USERPROFILE, set env.USERPROFILE = env.HOME. Tests now create a temporary HOME per test, pass it via runScript(..., { HOME: tmpHome }), and ensure cleanup with fs.rmSync(tmpHome, { recursive: true, force: true }) in finally blocks. Also removed outer parentheses around test(...) calls and standardized passed++/failed++ usage.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through envs, tidy and neat,
HOME mirrored for Windows — what a feat,
Temp homes made, then cleared away,
Five little tests now bounce and play,
Hooray — clean runs all day! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: fixing Windows cost-tracker HOME environment variable override handling in tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Important

Merge conflicts detected (Beta)

  • Resolve merge conflict in branch fix/windows-cost-tracker-home
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 1 file

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 20, 2026

Greptile Summary

This PR fixes cross-platform test failures in tests/hooks/cost-tracker.test.js by mirroring HOME to USERPROFILE in the runScript helper's env construction. Because os.homedir() on Windows reads USERPROFILE rather than HOME, tests that only set HOME were inadvertently writing metrics to the real home directory on Windows instead of the temp directory.

Key changes:

  • runScript() now detects when HOME is set in env overrides (using the in operator to correctly handle falsy values) and automatically adds a matching USERPROFILE, unless the caller already provides one — ensuring os.homedir() resolves correctly on Windows.
  • Test 1 ("passes through input on stdout") now redirects writes to a temporary HOME directory, preventing silent pollution of the developer's or CI runner's actual ~/.claude/metrics/costs.jsonl. This addresses the issue raised in a previous review thread.
  • All five test cases are refactored to use try/finally blocks for guaranteed temp-directory cleanup, replacing the earlier pattern where cleanup was skipped if an assertion threw.

Confidence Score: 5/5

  • This PR is safe to merge — it is a test-only change with no production code modifications.
  • All changes are confined to the test file. The HOME→USERPROFILE mirroring logic uses the in operator correctly (handles falsy values), the spread order is correct (caller-supplied USERPROFILE is never overridden), and the try/finally cleanup pattern is an improvement over the previous approach. The fix for Test 1 (redirecting to tmpHome) closes the last known gap where real home directory pollution could occur.
  • No files require special attention.

Important Files Changed

Filename Overview
tests/hooks/cost-tracker.test.js Refactored test helper to mirror HOME→USERPROFILE for Windows compatibility and wrapped all 5 test cases in try/finally for safe cleanup; test 1 now redirects metrics to a tmpHome instead of the real home directory.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["runScript(input, envOverrides)"] --> B{"'HOME' in envOverrides?"}
    B -- No --> D["env = { ...process.env, ...envOverrides }"]
    B -- Yes --> C{"'USERPROFILE' in envOverrides?"}
    C -- Yes --> D
    C -- No --> E["env = { ...process.env, ...envOverrides, USERPROFILE: envOverrides.HOME }"]
    D --> F["spawnSync('node', [cost-tracker.js], { env })"]
    E --> F
    F --> G["cost-tracker.js reads stdin"]
    G --> H["os.homedir() → resolves HOME (POSIX) or USERPROFILE (Windows)"]
    H --> I["Write metrics to tmpHome/.claude/metrics/costs.jsonl"]
    I --> J["stdout.write(raw) — pass-through"]
    J --> K["Test assertions"]
    K --> L{"Pass?"}
    L -- Yes --> M["passed++"]
    L -- No --> N["failed++"]
    M & N --> O["finally: fs.rmSync(tmpHome)"]
Loading

Last reviewed commit: "fix(tests): immutabl..."

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes Windows-specific failures in the cost-tracker hook tests by ensuring that when the tests override HOME (to redirect metrics output into a temp directory), the same override is also applied to USERPROFILE, which is what os.homedir() uses on Windows.

Changes:

  • Update runScript() in the cost-tracker test to mirror HOMEUSERPROFILE when HOME is overridden.
  • Refactor the per-test pass/fail counter updates into a more readable multiline ternary form.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const inputStr = typeof input === 'string' ? input : JSON.stringify(input);
// Mirror HOME to USERPROFILE for Windows (os.homedir() checks USERPROFILE on Windows)
const env = { ...process.env, ...envOverrides };
if (envOverrides.HOME && !envOverrides.USERPROFILE) {
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The HOME→USERPROFILE mirroring check uses truthiness (envOverrides.HOME && !envOverrides.USERPROFILE). This will treat an explicitly-provided but falsy USERPROFILE (e.g. '') as “not provided” and overwrite it, and it won’t run if HOME is intentionally set to a falsy value. Elsewhere in this repo, similar helpers use typeof ... === 'string' and !('USERPROFILE' in envOverrides) (e.g. tests/scripts/ecc.test.js:18-24, tests/scripts/session-inspect.test.js:20-26); consider aligning to that pattern here (and optionally mirroring both directions for consistency).

Suggested change
if (envOverrides.HOME && !envOverrides.USERPROFILE) {
if (typeof envOverrides.HOME === 'string' && !('USERPROFILE' in envOverrides)) {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/hooks/cost-tracker.test.js (1)

33-37: Good fix for Windows cross-platform compatibility.

The approach correctly mirrors HOME to USERPROFILE since os.homedir() on Windows checks USERPROFILE first. The condition properly preserves an explicit USERPROFILE if the caller provides one.

Consider a fully immutable pattern to align with coding guidelines:

♻️ Optional: Single-expression immutable construction
-  const env = { ...process.env, ...envOverrides };
-  if (envOverrides.HOME && !envOverrides.USERPROFILE) {
-    env.USERPROFILE = envOverrides.HOME;
-  }
+  const env = {
+    ...process.env,
+    ...envOverrides,
+    ...(envOverrides.HOME && !envOverrides.USERPROFILE && { USERPROFILE: envOverrides.HOME })
+  };

Based on learnings: "Ensure cross-platform compatibility for Windows, macOS, and Linux via Node.js scripts". As per coding guidelines: "Always create new objects instead of mutating existing ones."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/hooks/cost-tracker.test.js` around lines 33 - 37, The current code
creates env then mutates it to mirror HOME to USERPROFILE; change this to a
fully immutable single-expression construction that sets USERPROFILE
conditionally when building env rather than assigning to env after creation—use
process.env and envOverrides in the object spread and add USERPROFILE only if
envOverrides.HOME exists and envOverrides.USERPROFILE does not, so there is no
post-creation mutation of the env variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/hooks/cost-tracker.test.js`:
- Around line 33-37: The current code creates env then mutates it to mirror HOME
to USERPROFILE; change this to a fully immutable single-expression construction
that sets USERPROFILE conditionally when building env rather than assigning to
env after creation—use process.env and envOverrides in the object spread and add
USERPROFILE only if envOverrides.HOME exists and envOverrides.USERPROFILE does
not, so there is no post-creation mutation of the env variable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2f563685-d531-4b68-a21e-a90546d07efe

📥 Commits

Reviewing files that changed from the base of the PR and between a2df939 and 4abe18e.

📒 Files selected for processing (1)
  • tests/hooks/cost-tracker.test.js

- Use `in` operator for env override checks instead of truthiness,
  matching the pattern used elsewhere in the repo (handles falsy values)
- Redirect test 1 (passes through input) to a temp directory so it
  doesn't write metrics to the real home directory

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
@chris-yyau
Copy link
Copy Markdown
Contributor Author

Review feedback addressed (b960e3c)

Copilot: Switched env override check from truthiness (envOverrides.HOME && !envOverrides.USERPROFILE) to in operator ('HOME' in envOverrides && !('USERPROFILE' in envOverrides)) — handles falsy values correctly, matches repo convention.

Greptile: Test 1 (passes through input on stdout) now redirects to a temp directory via { HOME: tmpHome } so it no longer writes metrics to the real home directory.


The 9 Windows test failures are pre-existing on main (same jobs fail on the latest main CI run). Our fix resolves the cost-tracker tests — all 5 pass on Windows:

✓ passes through input on stdout
✓ creates metrics file when given valid usage data
✓ handles empty input gracefully
✓ handles invalid JSON gracefully
✓ handles missing usage fields gracefully
Results: Passed: 5, Failed: 0

Remaining Windows failures are bash-on-Windows issues in validators.test.js, detect-project-worktree.test.js, and observe.sh tests — none related to this PR.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/hooks/cost-tracker.test.js (1)

33-37: Avoid mutating env after creation in runScript.

Line 36 mutates env directly. Build env immutably in one expression so the helper remains side-effect-safe and guideline-compliant.

Suggested refactor
 function runScript(input, envOverrides = {}) {
   const inputStr = typeof input === 'string' ? input : JSON.stringify(input);
   // Mirror HOME to USERPROFILE for Windows (os.homedir() checks USERPROFILE on Windows)
-  const env = { ...process.env, ...envOverrides };
-  if ('HOME' in envOverrides && !('USERPROFILE' in envOverrides)) {
-    env.USERPROFILE = envOverrides.HOME;
-  }
+  const shouldMirrorHome = 'HOME' in envOverrides && !('USERPROFILE' in envOverrides);
+  const env = {
+    ...process.env,
+    ...envOverrides,
+    ...(shouldMirrorHome ? { USERPROFILE: envOverrides.HOME } : {})
+  };
   const result = spawnSync('node', [script], {

As per coding guidelines, "Always create new objects, never mutate existing ones" and "Use spread operator for immutable updates in TypeScript/JavaScript instead of direct mutation".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/hooks/cost-tracker.test.js` around lines 33 - 37, The test helper
mutates the local env object after creation; instead, build env immutably in one
expression inside runScript by combining process.env and envOverrides and
conditionally adding USERPROFILE when HOME is present and USERPROFILE is not
(avoid any assignment to env after creation). Locate the env construction in
runScript (the const env = { ... } block) and replace it with a single spread
expression that includes a conditional spread for USERPROFILE (e.g., use
...(envOverrides.HOME && !envOverrides.USERPROFILE ? { USERPROFILE:
envOverrides.HOME } : {})) so no mutation occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/hooks/cost-tracker.test.js`:
- Around line 54-66: Wrap each test body that creates tmpHome (e.g., the "passes
through input on stdout" test using tmpHome, runScript and fs.rmSync) in a
try/finally so fs.rmSync(tmpHome, { recursive: true, force: true }) always runs;
specifically, move the test logic into the try block and put the fs.rmSync call
in the finally block (apply the same pattern to the other affected tests at
lines 71-94, 97-107, 110-121, 124-143) to guarantee cleanup even if assertions
fail.

---

Nitpick comments:
In `@tests/hooks/cost-tracker.test.js`:
- Around line 33-37: The test helper mutates the local env object after
creation; instead, build env immutably in one expression inside runScript by
combining process.env and envOverrides and conditionally adding USERPROFILE when
HOME is present and USERPROFILE is not (avoid any assignment to env after
creation). Locate the env construction in runScript (the const env = { ... }
block) and replace it with a single spread expression that includes a
conditional spread for USERPROFILE (e.g., use ...(envOverrides.HOME &&
!envOverrides.USERPROFILE ? { USERPROFILE: envOverrides.HOME } : {})) so no
mutation occurs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99b6d9ec-cd06-4aa2-b30e-a7fa937dca47

📥 Commits

Reviewing files that changed from the base of the PR and between 4abe18e and b960e3c.

📒 Files selected for processing (1)
  • tests/hooks/cost-tracker.test.js

Address review nitpicks on PR affaan-m#700:
- Build env object immutably via conditional spread instead of post-creation mutation
- Wrap all 5 test bodies in try/finally to guarantee temp dir cleanup on assertion failure

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/hooks/cost-tracker.test.js (1)

55-155: Optional: extract a shared helper for temp HOME test scaffolding.

The repeated makeTempDir + try/finally pattern is solid, but a small helper (e.g., withTempHome) could reduce duplication and keep each test focused on assertions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/hooks/cost-tracker.test.js` around lines 55 - 155, Tests repeat
makeTempDir() + try/finally cleanup; extract a small helper (e.g., withTempHome)
that wraps makeTempDir, accepts a callback which receives the tmpHome, runs the
test body, and always removes the temp dir in a finally/cleanup block so each
test calls withTempHome(tmpHome => { ... }) instead of duplicating
makeTempDir/try/finally; update tests that reference makeTempDir and runScript
to use withTempHome while keeping assertions unchanged (refer to tests 'passes
through input on stdout', 'creates metrics file...', 'handles empty input...',
'handles invalid JSON...', and 'handles missing usage fields gracefully' and the
runScript helper).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/hooks/cost-tracker.test.js`:
- Around line 55-155: Tests repeat makeTempDir() + try/finally cleanup; extract
a small helper (e.g., withTempHome) that wraps makeTempDir, accepts a callback
which receives the tmpHome, runs the test body, and always removes the temp dir
in a finally/cleanup block so each test calls withTempHome(tmpHome => { ... })
instead of duplicating makeTempDir/try/finally; update tests that reference
makeTempDir and runScript to use withTempHome while keeping assertions unchanged
(refer to tests 'passes through input on stdout', 'creates metrics file...',
'handles empty input...', 'handles invalid JSON...', and 'handles missing usage
fields gracefully' and the runScript helper).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e3ee541d-543f-4668-a6d8-f1b2cef84af9

📥 Commits

Reviewing files that changed from the base of the PR and between b960e3c and 53d760a.

📒 Files selected for processing (1)
  • tests/hooks/cost-tracker.test.js

@affaan-m
Copy link
Copy Markdown
Owner

Superseded by #701 (Windows CI fix already merged)

@affaan-m affaan-m closed this Mar 20, 2026
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.

3 participants