Skip to content

test: skipif POSIX-only assertions on Windows (mode bits, fcntl, signal) — Windows CI PR 2/N#713

Merged
memtomem merged 1 commit intomainfrom
fix/windows-ci-posix-skipif
May 2, 2026
Merged

test: skipif POSIX-only assertions on Windows (mode bits, fcntl, signal) — Windows CI PR 2/N#713
memtomem merged 1 commit intomainfrom
fix/windows-ci-posix-skipif

Conversation

@memtomem
Copy link
Copy Markdown
Owner

@memtomem memtomem commented May 2, 2026

PR 2 of N in the Windows-CI green-up roadmap. (PR 1 was #711.)

Summary

Adds @pytest.mark.skipif(sys.platform == "win32", reason="…") to 9 tests across 5 files that exercise features which either don't exist on Windows or take a deliberately-different code path that's already covered by an existing test_windows_skips_kill-style sibling. Single uniform change axis — no assertion logic touched, no production source modified.

Cross-check table

Triaged against the latest Windows fail log (gh run view 25244446944 --log-failed --job=74026052967, 113 failures total).

Test Class Why POSIX-only
test_context_atomic.py::test_atomic_write_explicit_mode_0o600 mode bits NTFS ignores stat.S_IMODE
test_context_atomic.py::test_atomic_write_respects_custom_mode mode bits same
test_context_install.py::test_install_files_written_with_default_mode mode bits same
test_context_settings.py::TestClaudeSettingsAtomicWrite::test_mode_is_0o600 mode bits same
test_server_sigterm.py::test_legacy_lock_sh_allows_multiple_holders fcntl ModuleNotFoundError: No module named 'fcntl' at body-import time
test_upgrade_cmd.py::test_no_running_server_just_reinstalls signal/path Windows takes the "Detected Windows; skipping process termination" branch (covered by test_windows_skips_kill)
test_upgrade_cmd.py::test_running_server_sigterm_path signal exercises SIGTERM kill path; Windows skips it
test_upgrade_cmd.py::test_running_server_escalates_to_sigkill signal SIGKILL escalation path
test_upgrade_cmd.py::test_pid_file_unlink_skipped_if_respawned signal SIGKILL respawn-detection path

No Windows coverage is lost for the upgrade flowtest_windows_skips_kill (line 124 of test_upgrade_cmd.py) explicitly tests the Windows branch and continues to run.

Why skipif and not test rewrite

For the file-mode and fcntl tests, the property under test is meaningful only on POSIX. NTFS doesn't honour stat.S_IMODE and Python on Windows doesn't ship fcntl — there is nothing equivalent to assert.

For the four test_upgrade_cmd.py tests, the production code already has a Windows-aware branch (upgrade_cmd.py:256sys.platform == "win32" guard) and the existing test_windows_skips_kill already pins that branch's behaviour. Asserting a SIGTERM-shaped output on Windows would just duplicate the redirect that Windows test already covers.

Verification

  • uv run pytest on the 5 touched files (macOS): 91 passed.
  • uv run ruff check + format --check clean on the same files.

Windows CI impact

Measured on this branch's run (gh run view 25245004184 --log-failed --job=74027634112).

Metric Before (run 25244446944) After (run 25245004184)
Total Windows failures 113 105
POSIX-only candidates fixed 9 of 9 9 of 9 (all gone from fail list)
Net reduction −8

The net (−8) understates the POSIX-skipif contribution (−9) because the symlink / git-status failure class is flaky on Windows — same flake noise the PR #711 run also hit. Two more tests from that class disappeared between runs (test_cli_status_renders_states, test_dirty_partial_subdir) and three new ones appeared (test_stale_pin_when_history_rewritten, test_clean_immediately_after_install, test_skip_symlinks) — all in the same flaky class, none caused by this PR. They will be addressed in the dedicated symlink/git-status follow-up PR.

macOS / Ubuntu / lint / typecheck / golden-path / notebooks / CLA all green on this branch.

Out of scope (separate follow-up PRs)

Refs #637 (file-mode class umbrella). This PR doesn't touch the XDG_RUNTIME_DIR tests that #637 was originally written for, so leaving #637 open.

Test plan

  • Local pytest passes on touched files (macOS, 91 tests).
  • Ruff lint + format clean.
  • Windows CI failure count drops by 9 — all 9 targeted tests confirmed gone from the fail list (run 25245004184, net −8 due to symlink-class flake noise).
  • macOS / Ubuntu pass counts unchanged on this branch (both green; no regression).

🤖 Generated with Claude Code

PR 2 of N in the Windows-CI green-up roadmap (after #711 fixed the
path-separator class). Skips 9 tests across 5 files that exercise
features which either don't exist on Windows or take a deliberately
different code path that's already covered by an existing
`test_windows_skips_kill`-style sibling.

Pattern: single uniform `@pytest.mark.skipif(sys.platform == "win32",
reason="…")` decorator. No assertion logic touched, no production
source modified.

POSIX file mode (4 tests): Windows ignores POSIX permission bits
entirely, so `stat.S_IMODE(...)` always returns the NTFS-default 0o666 /
0o644 / 0o777 regardless of what the production code requests via
`os.chmod` or `os.fchmod`. The intent under test (atomic_write defaults
to 0o600 for state files, custom mode is honoured, install lands at
0o644) is meaningful only on POSIX.

- `test_context_atomic.py::test_atomic_write_explicit_mode_0o600`
- `test_context_atomic.py::test_atomic_write_respects_custom_mode`
- `test_context_install.py::test_install_files_written_with_default_mode`
- `test_context_settings.py::TestClaudeSettingsAtomicWrite::test_mode_is_0o600`

`fcntl` (1 test): the Python module doesn't exist on Windows and the
test imports it inside the function body, so on Windows the test
crashes with `ModuleNotFoundError: No module named 'fcntl'` at collect
time. The fix it pins (LOCK_SH + LOCK_NB coexistence on the same file)
is meaningful only on POSIX.

- `test_server_sigterm.py::test_legacy_lock_sh_allows_multiple_holders`

Signal / SIGKILL escalation (4 tests): the production path in
`upgrade_cmd` already takes a Windows-specific branch ("Detected
Windows; skipping process termination") that is fully covered by
`test_windows_skips_kill` (which we leave running on Windows). The
SIGTERM/SIGKILL escalation tests assert on POSIX-only output strings
and signal sequences and fail otherwise. Skipping them does NOT lose
Windows coverage of the upgrade flow — `test_windows_skips_kill` keeps
running.

- `test_upgrade_cmd.py::test_no_running_server_just_reinstalls`
- `test_upgrade_cmd.py::test_running_server_sigterm_path`
- `test_upgrade_cmd.py::test_running_server_escalates_to_sigkill`
- `test_upgrade_cmd.py::test_pid_file_unlink_skipped_if_respawned`

Out of scope (other Windows CI failure classes — separate PRs):

- `test_uninstall_cmd.py` failures: NOT POSIX-only mode bits — they
  surface NTFS file-locking semantics (.db / .db-shm / .db-wal aren't
  deleted while open, lock detection, inventory text differences).
  Multi-factor; deferred to PR 4/5.
- HOME monkeypatch reach (`Path.home()` vs `USERPROFILE`) — issue #644
- Path canonicalization in indexing — issue #647
- Symlink + git-status detection (flaky class)
- `'home'` fs alias / tilde expansion — issue #646
- `config_signature` NTFS mtime resolution — issue #645
- Wizard auto-detect Claude/Codex memory dirs — issue #316

Verification:

- `uv run pytest` on the 5 touched files (macOS): 91 passed.
- `uv run ruff check + format --check` clean.
- Windows CI before/after counts to be appended to the PR body once the
  branch run finishes; expected reduction is ~9 (all 9 targeted tests
  show up as SKIPPED on Windows, FAIL count drops accordingly).

Refs #637 (file-mode class umbrella; this PR doesn't touch the
XDG_RUNTIME_DIR tests #637 was originally written for, so leaving it
open).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@memtomem
Copy link
Copy Markdown
Owner Author

memtomem commented May 2, 2026

Windows CI delta (run 25245004184, job 74027634112)

Metric Before (PR #711 baseline, run 25244446944) After (this PR)
Total Windows failures 113 105
Net reduction −8

What actually changed

All 9 POSIX-only tests this PR targets are gone from the failure list ✅:

  • test_context_atomic.py::test_atomic_write_explicit_mode_0o600
  • test_context_atomic.py::test_atomic_write_respects_custom_mode
  • test_context_install.py::test_install_files_written_with_default_mode
  • test_context_settings.py::TestClaudeSettingsAtomicWrite::test_mode_is_0o600
  • test_server_sigterm.py::test_legacy_lock_sh_allows_multiple_holders
  • test_upgrade_cmd.py::test_no_running_server_just_reinstalls
  • test_upgrade_cmd.py::test_running_server_sigterm_path
  • test_upgrade_cmd.py::test_running_server_escalates_to_sigkill
  • test_upgrade_cmd.py::test_pid_file_unlink_skipped_if_respawned

The net is −8 (not −9) because the symlink / git-status failure class is flaky on Windows — same flake noise PR #711 saw. 2 tests from that class disappeared between runs (test_cli_status_renders_states, test_dirty_partial_subdir), 3 new ones appeared (test_stale_pin_when_history_rewritten, test_clean_immediately_after_install, test_skip_symlinks). All in the symlink/git-status class — not regressions from this PR.

Cumulative roadmap progress

Run Windows failures Net delta PRs landed
Pre-roadmap (run 25244100712, main) 117
After PR #711 (path-sep) 113 −4 1
After PR #713 (this, POSIX skipif) 105 −8 2

Negative pin (no regression on other OSes)

  • test (ubuntu-latest), test (macos-latest): SUCCESS
  • lint, typecheck, golden-path, notebooks, CLAAssistant: SUCCESS

✅ All required gates green; the Windows job is informational (continue-on-error: true).

@memtomem memtomem merged commit 0b2e183 into main May 2, 2026
8 of 9 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators May 2, 2026
@memtomem memtomem deleted the fix/windows-ci-posix-skipif branch May 2, 2026 05:54
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants