Skip to content

ci: add windows-latest to test matrix#65

Merged
wpak-ai merged 6 commits into
masterfrom
ci/windows-latest-matrix
Jun 3, 2026
Merged

ci: add windows-latest to test matrix#65
wpak-ai merged 6 commits into
masterfrom
ci/windows-latest-matrix

Conversation

@clean6378-max-it

@clean6378-max-it clean6378-max-it commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

closes #61

Summary

Add windows-latest to the CI test matrix so pytest, API integration tests, and Vitest run on both Ubuntu and Windows. Adds Windows-native tests for export-state locking (msvcrt.locking) and Claude projects path resolution (USERPROFILE).

Closes Chen June Week 1 sprint item #2 (5 pt).

Problem

CI previously ran only on ubuntu-latest. Two code paths differ on Windows but were never exercised on a real Windows runner:

  • utils/export_state_store.pymsvcrt.locking (existing tests only mocked msvcrt on Linux)
  • utils/session_path.pyget_claude_projects_dir() uses USERPROFILE when platform.system() == "Windows"

Changes

  • .github/workflows/ci.yml

    • Matrix os: [ubuntu-latest, windows-latest] with fail-fast: false for pytest, integration-tests, and js-tests
    • Coverage artifacts named coverage-report-${{ matrix.os }}
    • mypy and prod-install-smoke unchanged (Ubuntu only)
  • tests/test_export_state_store.py

    • test_export_state_lock_real_msvcrt_roundtrip — runs on win32 only; uses real msvcrt via export_state_lock + atomic_write_export_state
  • tests/test_session_path.py (new)

    • test_get_claude_projects_dir_uses_userprofile_on_windows — mocked Windows + USERPROFILE
    • test_get_claude_projects_dir_on_windows_runner — native Windows runner
  • CONTRIBUTING.md

    • Documents Ubuntu + Windows CI matrix

Test plan

  • pytest -q locally on Windows (348 passed)
  • npm test locally on Windows (39 passed)
  • GitHub Actions: pytest × 2 OS green
  • GitHub Actions: integration-tests × 2 OS green
  • GitHub Actions: js-tests × 2 OS green
  • GitHub Actions: mypy + prod-install-smoke still green on Ubuntu

Out of scope

Summary by CodeRabbit

  • Tests

    • Added Windows-specific tests for export-state locking and session-path handling to improve Windows compatibility and concurrency guarantees.
  • Chores

    • CI matrix expanded to run pytest, integration tests, and JS tests on both Ubuntu and Windows; per-OS coverage artifacts produced and matrix fail-fast disabled.
  • Documentation

    • CONTRIBUTING guide and PR checklist updated to reflect the expanded CI matrix and job expectations.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

CI jobs (pytest, integration-tests, js-tests) now run on ubuntu-latest and windows-latest; integration coverage artifacts are OS-specific. CONTRIBUTING.md updated. Added Windows-targeted tests for export-state locking and USERPROFILE-based session path resolution.

Changes

Windows CI Matrix and Platform-Specific Tests

Layer / File(s) Summary
CI matrix and job changes
.github/workflows/ci.yml
pytest, integration-tests, and js-tests jobs now run on a matrix.os (ubuntu-latest, windows-latest) with strategy.fail-fast: false; integration-tests coverage upload name changed to include ${{ matrix.os }} and pytest has a coverage-gate comment.
CONTRIBUTING update
CONTRIBUTING.md
Docs updated to state pytest, integration tests, and Vitest run on Ubuntu and Windows (Python 3.12 / Node 20); mypy and prod-install-smoke remain Ubuntu-only; PR checklist text adjusted.
Session path Windows tests
tests/test_session_path.py
Added a module docstring and two tests: a monkeypatched-Windows test and a native-windows-runner test (skipped unless sys.platform == "win32") validating get_claude_projects_dir() uses USERPROFILE/.claude/projects.
Export-state lock Windows tests
tests/test_export_state_store.py
Added Windows test scaffolding (REPO_ROOT, polling helpers), extended imports (atomic_write_export_state, export_state_lock), and two Windows-only tests exercising real msvcrt locking and cross-process exclusivity with child-process marker synchronization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • timon0305
  • wpak-ai

Poem

🐰 I hopped from branch to test and tray,
Added Windows runners to join the play,
Locks now click and paths find home,
Two OSes run so no branch must roam,
A carrot cheer for CI's wider day!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'ci: add windows-latest to test matrix' directly and accurately summarizes the main change—adding Windows to the CI matrix for testing.
Linked Issues check ✅ Passed The PR implements all core objectives from issue #61: Windows-latest matrix for pytest/integration/js-tests, Windows-specific msvcrt locking tests, USERPROFILE resolution tests on Windows runner, and updated documentation.
Out of Scope Changes check ✅ Passed All changes align with issue #61 scope: CI matrix expansion, platform-specific tests for export state locking and USERPROFILE resolution, and documentation updates. No out-of-scope modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/windows-latest-matrix

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
tests/test_export_state_store.py (1)

98-109: 💤 Low value

Strengthen the roundtrip assertions with distinctive payload values.

The payload uses exportedCount: 0 and sessions: {}, which coincide with the loader's default/sanitized outputs. A roundtrip that genuinely persists data would be better verified with non-default values (and asserting lastExportTime, which is written but never checked). This makes the test fail loudly if the atomic write didn't actually persist the intended content.

♻️ Proposed change to use distinctive values
     payload = {
-        "sessions": {},
+        "sessions": {"uuid-one": 1740000000.0},
         "lastExportTime": "2026-01-01T00:00:00",
-        "exportedCount": 0,
+        "exportedCount": 3,
     }

     with export_state_lock(str(state_file)):
         atomic_write_export_state(payload, str(state_file))

     loaded = load_export_state_from_disk(str(state_file))
-    assert loaded.get("exportedCount") == 0
-    assert loaded.get("sessions") == {}
+    assert loaded.get("exportedCount") == 3
+    assert loaded.get("sessions") == {"uuid-one": 1740000000.0}
+    assert loaded.get("lastExportTime") == "2026-01-01T00:00:00"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_export_state_store.py` around lines 98 - 109, The test uses
default-ish values that match loader defaults; change the payload in the test to
use distinctive, non-default values and assert all persisted fields (e.g., set
exportedCount to a non-zero number, add a non-empty sessions entry, and set a
unique lastExportTime string) so the roundtrip via export_state_lock ->
atomic_write_export_state and load_export_state_from_disk is actually validated;
update assertions to check exportedCount, sessions contents, and lastExportTime
against those distinctive values to ensure the write/read truly persisted the
payload.
tests/test_session_path.py (1)

36-37: ⚡ Quick win

Consider exact equality for more precise validation.

Since USERPROFILE is controlled and get_claude_projects_dir() deterministically returns os.path.join(str(profile), ".claude", "projects"), an exact equality assertion would be more rigorous than separate startswith and endswith checks.

Proposed refactor to use exact equality
-    assert got.startswith(str(profile))
-    assert got.endswith(os.path.join(".claude", "projects"))
+    assert got == os.path.join(str(profile), ".claude", "projects")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_session_path.py` around lines 36 - 37, Replace the loose
startswith/endswith assertions with a single exact equality check: assert got ==
os.path.join(str(profile), ".claude", "projects"). Update the test around
get_claude_projects_dir() (the variables got and profile) to compute the
expected path with os.path.join(str(profile), ".claude", "projects") and compare
directly for deterministic validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/test_export_state_store.py`:
- Around line 98-109: The test uses default-ish values that match loader
defaults; change the payload in the test to use distinctive, non-default values
and assert all persisted fields (e.g., set exportedCount to a non-zero number,
add a non-empty sessions entry, and set a unique lastExportTime string) so the
roundtrip via export_state_lock -> atomic_write_export_state and
load_export_state_from_disk is actually validated; update assertions to check
exportedCount, sessions contents, and lastExportTime against those distinctive
values to ensure the write/read truly persisted the payload.

In `@tests/test_session_path.py`:
- Around line 36-37: Replace the loose startswith/endswith assertions with a
single exact equality check: assert got == os.path.join(str(profile), ".claude",
"projects"). Update the test around get_claude_projects_dir() (the variables got
and profile) to compute the expected path with os.path.join(str(profile),
".claude", "projects") and compare directly for deterministic validation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 98eea4a1-cb1f-443f-bad7-26518c410f63

📥 Commits

Reviewing files that changed from the base of the PR and between 965618b and 9f3b3c6.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • CONTRIBUTING.md
  • tests/test_export_state_store.py
  • tests/test_session_path.py

@clean6378-max-it

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit , can you review this PR fully again?

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== .github/workflows/ci.yml ==="
cat .github/workflows/ci.yml

echo ""
echo "=== CONTRIBUTING.md ==="
cat CONTRIBUTING.md

echo ""
echo "=== tests/test_export_state_store.py ==="
cat tests/test_export_state_store.py

echo ""
echo "=== tests/test_session_path.py ==="
cat tests/test_session_path.py

echo ""
echo "=== utils/session_path.py (or similar) ==="
fd -e py session_path | xargs cat

echo ""
echo "=== Source: export_state_lock and atomic_write_export_state ==="
rg -n "def export_state_lock|def atomic_write_export_state" --type py -C 5

echo ""
echo "=== get_claude_projects_dir function ==="
rg -n "def get_claude_projects_dir" --type py -C 10

echo ""
echo "=== Full session_path module ==="
fd session_path.py | xargs cat

Length of output: 30031



@clean6378-max-it: Here is the full review of PR #65.


CI Workflow (.github/workflows/ci.yml)

Looks good overall. Matrix expansion is correct and complete.

  • fail-fast: false applied to all three matrix jobs (pytest, integration-tests, js-tests)
  • mypy and prod-install-smoke correctly remain Ubuntu-only
  • ✅ No bash-specific shell constructs in the matrix jobs — all pip/pytest/npm commands are cross-platform
  • ✅ Coverage artifact renamed to coverage-report-${{ matrix.os }}, which prevents artifact name collision when both Ubuntu and Windows upload in the same run
  • ⚠️ Note (non-blocking): The prod-install-smoke job uses a bash heredoc (python - <<'PY'). Since that job remains Ubuntu-only, it's safe today, but if it's ever extended to Windows it would need to be replaced with python -c "..." or a .py file invocation.

tests/test_export_state_store.py

The new test_export_state_lock_real_msvcrt_roundtrip test correctly exercises the real msvcrt.locking path on Windows and is properly guarded with @pytest.mark.skipif(sys.platform != "win32", ...).

Minor issue — incomplete roundtrip assertion:

The payload written includes lastExportTime, but the test only asserts exportedCount and sessions. Adding the third assertion would make the roundtrip complete:

loaded = load_export_state_from_disk(str(state_file))
assert loaded.get("exportedCount") == 0
assert loaded.get("sessions") == {}
assert loaded.get("lastExportTime") == "2026-01-01T00:00:00"  # add this

tests/test_session_path.py

Both tests are structurally correct and cover the required cases.

Minor issue — unnecessary directory creation in mocked test:

get_claude_projects_dir() constructs a path string; it does not check whether the directory exists. The profile.mkdir(parents=True) call in test_get_claude_projects_dir_uses_userprofile_on_windows is redundant:

# profile.mkdir(parents=True)  ← not needed; the function never calls os.path.isdir

Cosmetic issue — hardcoded username:

profile = tmp_path / "Users" / "chen"

The hardcoded "chen" leaks a personal username into a public test. Consider using a neutral name like "testuser":

profile = tmp_path / "Users" / "testuser"

Minor — delenv("HOME") is redundant in the mocked test:

The source code's Windows branch is:

home = os.environ.get("USERPROFILE", os.path.expanduser("~"))

Since USERPROFILE is explicitly set via monkeypatch.setenv, the expanduser("~") fallback (which reads HOME) is never reached. The monkeypatch.delenv("HOME", raising=False) call is defensive but has no effect on the outcome. It's not wrong, just slightly misleading as to what the test is actually verifying.


CONTRIBUTING.md

✅ Accurately reflects the new matrix. The PR checklist entry (pytest, integration-tests, js-tests on Ubuntu + Windows; mypy, prod-install-smoke on Ubuntu) matches the workflow exactly.


Summary

Severity File Finding
Minor tests/test_export_state_store.py Missing lastExportTime assertion in roundtrip test
Minor tests/test_session_path.py profile.mkdir(parents=True) is unnecessary
Cosmetic tests/test_session_path.py Hardcoded "chen" username
Cosmetic tests/test_session_path.py delenv("HOME") is redundant
Note .github/workflows/ci.yml prod-install-smoke bash heredoc is safe today (Ubuntu-only)

The PR is in good shape — the CI matrix expansion is correct, the Windows-specific tests are properly gated, and the coverage artifact naming fix is solid. The items above are all minor/cosmetic and don't block merging.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_export_state_store.py`:
- Around line 98-109: The test currently writes a payload with "lastExportTime"
via atomic_write_export_state inside export_state_lock but does not assert it
after loading; update the assertions after
load_export_state_from_disk(str(state_file)) to also assert that
loaded.get("lastExportTime") equals the original payload's "lastExportTime" (or
the exact string "2026-01-01T00:00:00") so the roundtrip for lastExportTime is
verified alongside exportedCount and sessions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1369347f-8d4b-449c-9813-6031a6b3932f

📥 Commits

Reviewing files that changed from the base of the PR and between 965618b and 9f3b3c6.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • CONTRIBUTING.md
  • tests/test_export_state_store.py
  • tests/test_session_path.py

Comment thread tests/test_export_state_store.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_export_state_store.py`:
- Around line 123-139: The current test uses time.sleep to assume the child has
attempted to acquire export_state_lock, which is flaky; change the child script
(the child string) to create a distinct "started" marker (e.g., started_marker)
immediately before attempting with export_state_lock(path) and have the parent
poll/wait for that started marker to appear (instead of sleeping) before
asserting marker.is_file() is false while holding the lock; use the existing
export_state_lock import and the child/proc flow so the parent reliably knows
the child has reached the lock attempt and the assertion becomes deterministic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a3e59bf1-712b-49a3-b5d7-a973053af5e6

📥 Commits

Reviewing files that changed from the base of the PR and between 9f3b3c6 and cac3994.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • tests/test_export_state_store.py
  • tests/test_session_path.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/ci.yml
  • tests/test_session_path.py

Comment thread tests/test_export_state_store.py Outdated
@clean6378-max-it clean6378-max-it self-assigned this Jun 3, 2026
@timon0305

Copy link
Copy Markdown
Collaborator

pytest runs without --cov; integration-tests re-runs a subset of the same tests with --cov. So on each OS the same files execute twice. If that's intentional (full-suite gate vs explicit coverage scope), one line of ci.yml saying so would prevent the next maintainer from "simplifying." If not, consolidating coverage into the main pytest job removes a Windows-runner job.

Comment thread tests/test_export_state_store.py Outdated
Comment thread tests/test_export_state_store.py Outdated
Comment thread tests/test_session_path.py
@clean6378-max-it

Copy link
Copy Markdown
Collaborator Author

Saying the main job runs “without --cov” is not accurate for this repo: pyproject.toml applies coverage to every pytest invocation unless you pass --no-cov. The duplication is re-running two test modules, not “no coverage in pytest

@clean6378-max-it

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit, reviewer's comments are addressed enough properly?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_export_state_store.py`:
- Around line 152-166: The test has a race where the child process (spawned via
subprocess.Popen) may acquire the lock before the parent calls
export_state_lock; fix it by moving the subprocess.Popen call inside the with
export_state_lock(str(state_file)) block so the parent holds the lock before
spawning the child; locate the block using export_state_lock, subprocess.Popen,
and the variables child, state_file, attempting_marker, acquired_marker (and
REPO_ROOT) and ensure the sequence becomes: with export_state_lock(...): start
Popen, then call _wait_for_file(attempting_marker) and
_assert_file_stays_absent(acquired_marker).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 27fc1867-9ce6-4881-9d47-20f600284213

📥 Commits

Reviewing files that changed from the base of the PR and between a000e30 and dd06045.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • tests/test_export_state_store.py
  • tests/test_session_path.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/ci.yml
  • tests/test_session_path.py

Comment thread tests/test_export_state_store.py Outdated
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cat .github/workflows/ci.yml

Length of output: 4763



@clean6378-max-it: Yes, timon0305's comment is properly addressed:

  1. Factual correction — your inline reply correctly explained that pyproject.toml applies --cov to every pytest invocation, so timon0305's premise ("pytest runs without --cov") was wrong. The actual overlap is re-running two specific test modules.

  2. Clarifying comment in ci.yml — timon0305's core ask was "if it's intentional, one line of ci.yml saying so would prevent the next maintainer from simplifying." Both comments are now present in the workflow:

    • pytest job: # Full suite gate (pyproject addopts include --cov + 60% fail-under).
    • integration-tests job: # Intentional overlap with pytest job: re-runs API/search subset with --cov / and uploads coverage.xml per OS (pytest job is the full-suite gate).

That directly satisfies what was requested. The reviewer's concern is fully addressed. ✅

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_export_state_store.py`:
- Around line 152-165: The type annotation for proc does not match the actual
return type of subprocess.Popen() here; update the annotation from
subprocess.Popen[str] to subprocess.Popen[bytes] (or alternatively add text=True
to the subprocess.Popen(...) call) so the declared type of proc matches the
Popen invoked inside the with export_state_lock(...) block; adjust the
annotation on the proc variable accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f16fec22-8d05-44b7-b4da-d6755ba0ce05

📥 Commits

Reviewing files that changed from the base of the PR and between dd06045 and 88bf398.

📒 Files selected for processing (1)
  • tests/test_export_state_store.py

Comment thread tests/test_export_state_store.py Outdated
@clean6378-max-it clean6378-max-it requested a review from wpak-ai June 3, 2026 22:10
@wpak-ai wpak-ai merged commit 44942f9 into master Jun 3, 2026
9 checks passed
@wpak-ai wpak-ai deleted the ci/windows-latest-matrix branch June 3, 2026 23:00
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.

claude-code-chat-browser: Add windows-latest to CI test matrix

3 participants