Skip to content

Conversation

@akesson
Copy link
Member

@akesson akesson commented Nov 27, 2025

Summary

  • Add Windows support via interprocess crate (named pipes)
  • Add comprehensive test coverage for identified gaps
  • Add GitHub Actions CI with fmt, clippy, and nextest (Linux, macOS, Windows)

Test plan

  • CI passes on all platforms
  • Tests pass locally on macOS/Linux
  • Windows builds and tests pass in CI

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Cross-platform IPC and process control enabling Windows support and multi‑OS client/server operation
    • Improved daemon lifecycle with more reliable startup, restart and stale-process/socket cleanup
  • Bug Fixes

    • Reduced termination/readiness races to decrease hangs and failed commands
  • Tests

    • Expanded integration tests for streaming, concurrency, cleanup, permissions and versioning
  • Chores

    • Release 0.6.0, CI workflow (format/lint/multi‑platform tests) and local Git hooks
  • Examples

    • Adjusted command parsing to change whitespace handling

✏️ Tip: You can customize this high-level summary in your review settings.

akesson and others added 3 commits November 27, 2025 18:33
- Replace Unix domain sockets with interprocess local sockets (Unix
  sockets on Unix, named pipes on Windows)
- Add process.rs module for cross-platform process management
- Move nix crate to Unix-only, add windows-sys for Windows
- Update force_stop() to use platform-specific termination
- Document Windows limitation: no graceful shutdown (immediate kill)

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

Co-Authored-By: Claude <noreply@anthropic.com>
- Add 9 tests for ErrorContextBuffer (circular eviction, cloning, timestamps)
- Add 4 process termination tests (SIGTERM→SIGKILL escalation, Windows)
- Add 8 integration tests (panic recovery, large output, stale cleanup, stress)
- Add 2 version tests (mismatch detection, protocol behavior)
- Add Debug derive to TerminateResult for better test output

Total test count increased from ~30 to 57 tests.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Runs on Linux, macOS, and Windows.

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

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Nov 27, 2025

Walkthrough

Adds cross-platform IPC and process management, refactors client lifecycle to use them (startup, force-stop, restart), expands tests and examples, bumps package to 0.6.0 with new platform deps, adds CI + git hooks, and ignores /.cache in VCS. (50 words)

Changes

Cohort / File(s) Change Summary
CI & VCS
/.github/workflows/ci.yml, /.gitignore
Adds GitHub Actions CI with jobs fmt, clippy, and test (cargo nextest) across an OS matrix with caching; adds /.cache to .gitignore.
Manifest / deps
Cargo.toml
Bumps package to 0.6.0; adds interprocess = { version = "2.2", features = ["tokio"] }, widestring = "1.0", target deps nix = { version = "0.30.1", features = ["signal"] } for Unix and windows-sys = { version = "0.61.2", features = ["Win32_Foundation", "Win32_System_Threading"] } for Windows; adds dev-dep cargo-husky.
Pre-commit / pre-push hooks
.cargo-husky/hooks/pre-commit, .cargo-husky/hooks/pre-push
Adds pre-commit hook running cargo fmt --check and cargo clippy (errors as warnings), and pre-push hook running cargo nextest run.
Core module surface
src/lib.rs
Declares new process module (mod process;).
Process management
src/process.rs
New cross-platform process API: TerminateResult enum and public functions process_exists(), async terminate_process(pid, timeout_ms), and async kill_process(pid) with Unix (signals/escalation) and Windows (Win32/taskkill-style) implementations and tests.
Transport / IPC
src/transport.rs
Replaces Unix-only socket types with platform-abstracted Listener/Stream, adds socket_name() and daemon_socket_exists(), introduces SocketServer, SocketClient, SocketConnection, SocketMessage (platform-aware framing), and removes Unix-only Drop cleanup.
Client lifecycle
src/client.rs
Refactors startup/stop/restart to use process and transport helpers (daemon_socket_exists, process_exists, terminate_process, kill_process); centralizes stale-process cleanup, readiness polling, and unified restart/retry logic.
Error context tests
src/error_context.rs
Adds comprehensive #[cfg(test)] tests for ErrorContextBuffer (init, eviction, clone, dump ordering).
Integration tests
tests/integration_tests.rs
Adds many integration tests and handlers covering streaming races, large output, panics, stale socket/PID cleanup, rapid connect/disconnect stress, connection limits, concurrency, UNIX socket permissions; adds start/stop test daemon helpers.
Version tests
tests/version_tests.rs
Adds tests for version mismatch behavior and multiple commands per connection.
Examples / CLI handling
examples/common/mod.rs, examples/concurrent.rs
Minor parsing changes: use command.split_whitespace() and parts.first() (changes whitespace handling).
Unit test tweaks
src/tests.rs
Minor assertion and formatting adjustments in tests.
Changelog
CHANGELOG.md
Adds 0.6.0 entry documenting Windows support and git hooks.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant Transport as SocketClient/SocketConnection
    participant Daemon as SocketServer
    participant Process as process.rs
    Note over Client,Transport: Client issues connect or command
    Client->>Transport: connect(daemon_name, root_path)
    alt socket present & usable
        Transport->>Daemon: accept connection
        Client->>Daemon: VersionHandshake / Command
        Daemon-->>Client: OutputChunk / CommandComplete
    else socket missing or stale
        Client->>Process: process_exists(pid)?
        Process-->>Client: true / false
        alt stale pid/socket detected
            Client->>Process: terminate_process(pid, timeout_ms)
            Process-->>Client: Terminated / AlreadyDead / PermissionDenied / Error
            Client->>Transport: retry connect or spawn daemon
        end
    end
    Note over Process,Daemon: kill_process used for force-stop or escalation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing extra attention:
    • src/process.rs: platform-specific termination semantics, async timeout/polling, mapping of TerminateResult variants, and cross-platform test behavior.
    • src/transport.rs and src/client.rs: correctness of cross-platform Stream/Listener abstractions, socket naming, existence checks, race conditions during startup/retry and cleanup paths.
    • tests/integration_tests.rs: many concurrent and stress tests—verify isolation, determinism, and resource cleanup to avoid CI flakiness.
    • Cargo.toml: ensure target-specific dependency declarations and feature flags are correct.

Possibly related PRs

Poem

🐇 I hopped through pipes and cross‑platform streams,

chased stubborn PIDs and debugged dreams,
CI hums, tests dance in nightly light,
sockets wake and daemons bite,
a tiny rabbit cheers—code stitched tight!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Windows support and CI' directly summarizes the main changes: introducing Windows support and adding GitHub Actions CI configuration across multiple platforms.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch win-support

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

Copy link

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/client.rs (2)

499-500: Fix collapsible-if clippy warning.

The nested if statements can be collapsed into a single condition.

Apply this diff:

-        if let Err(ref error) = result {
-            if self.auto_restart_on_error && Self::is_fatal_connection_error(error) {
+        if let Err(ref error) = result
+            && self.auto_restart_on_error
+            && Self::is_fatal_connection_error(error)
+        {

544-548: Use inspect_err instead of map_err.

Since the closure returns the error unchanged, inspect_err is more idiomatic.

Apply this diff:

-            .map_err(|e| {
+            .inspect_err(|_| {
                 self.error_context.dump_to_stderr();
-                e
             })?;
🧹 Nitpick comments (2)
.github/workflows/ci.yml (1)

14-21: fmt job: Consider running on all platforms for consistency.

While code formatting is platform-independent and running only on ubuntu-latest is a reasonable optimization, some projects benefit from running fmt checks across all platforms to catch any platform-specific formatting inconsistencies or locale-related issues early. The current approach is acceptable; this is optional.

tests/version_tests.rs (1)

347-397: Consider consolidating with test_version_mismatch_detection.

This test (test_version_mismatch_triggers_client_action) is nearly identical to test_version_mismatch_detection (lines 75-125). The only differences are the daemon/client timestamps and slightly different assertion messages. Consider whether both tests provide unique value or if one could be removed to reduce redundancy.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b73324 and c7154f1.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml (1 hunks)
  • .gitignore (1 hunks)
  • Cargo.toml (1 hunks)
  • src/client.rs (6 hunks)
  • src/error_context.rs (1 hunks)
  • src/lib.rs (1 hunks)
  • src/process.rs (1 hunks)
  • src/transport.rs (4 hunks)
  • tests/integration_tests.rs (1 hunks)
  • tests/version_tests.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
tests/version_tests.rs (3)
src/server.rs (1)
  • new_with_name_and_timestamp (140-162)
src/client.rs (1)
  • connect (59-71)
src/transport.rs (1)
  • connect (149-166)
tests/integration_tests.rs (3)
src/lib.rs (1)
  • handle (272-278)
src/client.rs (1)
  • connect_with_name_and_timestamp (81-192)
src/transport.rs (3)
  • socket_path (43-47)
  • socket_path (138-140)
  • pid_path (50-54)
src/client.rs (2)
src/process.rs (6)
  • kill_process (41-43)
  • kill_process (112-115)
  • kill_process (176-182)
  • process_exists (20-22)
  • process_exists (52-59)
  • process_exists (128-139)
src/transport.rs (3)
  • daemon_socket_exists (76-87)
  • socket_path (43-47)
  • socket_path (138-140)
🪛 GitHub Actions: CI
tests/version_tests.rs

[error] 1-1: cargo fmt --check failed. Run 'cargo fmt' to format the code.

src/process.rs

[error] 1-1: cargo fmt --check failed. Run 'cargo fmt' to format the code.

tests/integration_tests.rs

[error] 1-1: cargo fmt --check failed. Run 'cargo fmt' to format the code.

src/client.rs

[error] 499-499: collapsible-if: this if statement can be collapsed. Clippy note: collapse nested if block. Command 'cargo clippy --all-targets --all-features -- -D warnings' failed with exit code 101.


[error] 545-545: manual-inspect: using map_err over inspect_err. Clippy suggest: use '.inspect_err' or adjust accordingly. Command 'cargo clippy --all-targets --all-features -- -D warnings' failed with exit code 101.

src/transport.rs

[error] 1-1: cargo fmt --check failed. Run 'cargo fmt' to format the code.

🔇 Additional comments (33)
.github/workflows/ci.yml (3)

1-47: Solid CI workflow structure for cross-platform Rust project.

The workflow is well-organized with proper job dependency ordering, platform-specific matrix testing, and caching for efficiency. The three-job structure (fmt → clippy → test) enforces code quality gates before running tests, which is a best practice. Cross-platform testing on Linux, macOS, and Windows aligns well with the PR objective of adding Windows support.


9-11: RUSTFLAGS configuration treats warnings as errors—good practice.

Using -D warnings ensures the codebase remains warning-free across all platforms. However, verify that this doesn't cause issues with generated code or platform-specific dependencies that might emit warnings outside your control.


33-33: Cache configuration: Verify cache key consistency across platforms.

Both clippy and test jobs use Swatinem/rust-cache@v2. Ensure that the cache keys are correctly differentiated by platform (os matrix) so that platform-specific artifacts don't interfere with one another. The action should handle this automatically, but it's worth confirming in your first run.

Also applies to: 45-45

.gitignore (1)

1-1: LGTM!

Adding /.cache to .gitignore is appropriate for ignoring local development/runtime cache directories.

src/error_context.rs (1)

159-278: Good test coverage for ErrorContextBuffer.

The tests comprehensively cover buffer initialization, entry management, circular eviction behavior, cloning semantics, and timestamp ordering. The test cases align well with the implementation details.

src/transport.rs (2)

68-87: Good cross-platform socket existence check.

The platform-aware approach is well thought out—checking the socket file on Unix and using the PID file as a proxy on Windows (since named pipes aren't filesystem entities). This provides a consistent API while respecting platform differences.


148-166: Clean cross-platform client connection implementation.

The platform-specific connection logic is well-structured using conditional compilation, keeping the public API consistent across platforms.

tests/integration_tests.rs (10)

776-798: LGTM!

The ImmediateOutputHandler correctly tests the race condition scenario where the handler completes before output is fully read. The chunked write pattern is appropriate for this test case.


831-857: LGTM!

The LargeOutputHandler is well-designed for testing large output streaming with realistic delays. The 8KB chunk size is reasonable for buffer testing.


889-905: LGTM!

The PanicHandler correctly tests panic handling in the daemon. The test verifies server resilience after a handler panic.


800-829: LGTM!

This test correctly validates the critical fix for handler output/task completion race conditions. Using 64KB of output is appropriate for testing buffering behavior.


859-887: LGTM!

Testing 1MB output streaming validates the daemon's ability to handle large data transfers reliably.


907-956: LGTM!

Excellent test coverage for panic handling. The test correctly verifies both error reporting and server resilience after a handler panic.


958-999: LGTM!

This test validates stale socket and PID file cleanup, which is essential for reliable daemon operation after crashes or unclean shutdowns.


1001-1071: LGTM!

Good stress test for connection handling. The 80% success threshold (40/50) is reasonable to account for timing-related variations while still ensuring server stability.


1073-1169: LGTM!

This test thoroughly validates the connection limit and immediate rejection behavior. The inline SlowHandler definition keeps the test self-contained and readable.


1175-1202: LGTM!

Excellent security test verifying Unix socket permissions are set to 0600 (owner read/write only). This ensures proper access control for the daemon socket.

src/lib.rs (1)

108-108: LGTM!

Adding the process module integrates the platform-aware process management functionality needed for Windows support.

src/client.rs (5)

2-4: LGTM!

Good refactoring to use the new cross-platform abstractions from process and transport modules.


105-109: LGTM!

Using daemon_socket_exists provides proper cross-platform socket existence checking (file-based on Unix, PID file-based on Windows).


296-296: LGTM!

Using kill_process from the process module ensures cross-platform process termination.


304-332: LGTM!

The documentation clearly explains the platform-specific behavior differences between Unix (graceful shutdown with SIGTERM/SIGKILL) and Windows (immediate termination).


353-395: LGTM!

The force_stop implementation correctly uses the cross-platform process_exists and terminate_process functions. The TerminateResult handling covers all variants with appropriate error messages.

src/process.rs (10)

6-17: LGTM!

The TerminateResult enum comprehensively covers all process termination outcomes, enabling proper error handling in the client.


19-43: LGTM!

Clean public API with clear documentation about platform-specific behavior. The async functions appropriately use async operations for waiting.


52-59: LGTM!

The Unix process_exists implementation correctly uses kill(pid, None) to check process existence. The EPERM handling (line 56) correctly returns true since the process exists even if we can't signal it.


61-110: LGTM!

The Unix terminate_process implementation correctly implements the SIGTERM → wait → SIGKILL escalation pattern. The error handling covers all relevant Errno cases.


128-139: LGTM!

The Windows process_exists correctly uses OpenProcess with PROCESS_QUERY_LIMITED_INFORMATION to check existence without requiring full process access.


141-174: LGTM!

The Windows terminate_process correctly handles the lack of graceful shutdown (no SIGTERM equivalent). The permission check fallback (lines 150-157) properly distinguishes between non-existent and permission-denied cases.


185-218: LGTM!

Good basic unit tests for process existence and termination of non-existent processes. Using PID 4_000_000_000 safely tests non-existent process handling.


219-260: LGTM!

Excellent Unix-specific test with proper documentation about zombie process behavior. The test correctly reaps the child process to verify cleanup.


262-308: LGTM!

Good test for SIGTERM → SIGKILL escalation by using a process that traps SIGTERM. The zombie handling is properly documented.


310-345: LGTM!

Appropriate Windows-specific test using the timeout command. The simpler assertion (only Terminated) reflects Windows' immediate termination behavior.

Comment on lines +100 to +109
#[cfg(unix)]
let listener = {
// Remove existing socket file if it exists
if sock_path.exists() {
fs::remove_file(&sock_path)?;
}
ListenerOptions::new()
.name(sock_path.clone().to_fs_name::<GenericFilePath>()?)
.create_tokio()?
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how the caller coordinates daemon cleanup before creating a new server
rg -nA10 'SocketServer::new' --type rust

Repository: human-solutions/daemon-cli

Length of output: 796


🏁 Script executed:

#!/bin/bash
# Look at broader context before SocketServer::new call
rg -nB20 'SocketServer::new' --type rust src/server.rs | head -60

echo "---"

# Check if there's any daemon coordination before server creation
rg -n 'pid_path\|PID\|daemon.*check\|already.*running' --type rust src/server.rs | head -20

Repository: human-solutions/daemon-cli

Length of output: 1217


🏁 Script executed:

#!/bin/bash
# Look at the socket cleanup code in transport.rs (lines 100-109)
sed -n '90,120p' src/transport.rs

echo "---"

# Look at the full SocketServer::new function
rg -n 'impl.*SocketServer\|fn new' --type rust src/transport.rs | head -10

Repository: human-solutions/daemon-cli

Length of output: 1073


🏁 Script executed:

#!/bin/bash
# Look at the broader server.rs file to see if there's daemon coordination before run()
rg -n 'fn run\|async fn run\|daemon.*exist\|pid.*check\|already.*running' --type rust src/server.rs

echo "---"

# Check the entry point - how is the server started
rg -n 'main\|pub async fn\|impl.*Server' --type rust src/server.rs | head -20

echo "---"

# Look for any PID file checking logic
rg -n 'pid_path\|read.*pid\|PID' --type rust src/ --type rust

Repository: human-solutions/daemon-cli

Length of output: 77


🏁 Script executed:

#!/bin/bash
# Find main.rs or lib.rs
find . -type f -name "main.rs" -o -name "lib.rs" | head -5

echo "---"

# Search for any daemon start/stop/status commands
rg -n 'start\|stop\|status' --type rust | grep -i daemon | head -20

echo "---"

# Look for the full context of how SocketServer::new is called
sed -n '150,210p' src/server.rs

Repository: human-solutions/daemon-cli

Length of output: 2592


🏁 Script executed:

#!/bin/bash
# Look at lib.rs
cat -n src/lib.rs

echo "---"

# Check the transport module for pid_path function
rg -nA5 'fn pid_path' --type rust src/transport.rs

Repository: human-solutions/daemon-cli

Length of output: 11329


🏁 Script executed:

#!/bin/bash
# Look at the client.rs to see how client connects
rg -n 'DaemonClient\|connect\|daemon.*exist\|check.*pid' --type rust src/client.rs | head -30

echo "---"

# Look for any version checking or daemon restart logic
rg -n 'mtime\|version\|restart\|existing' --type rust src/client.rs | head -20

Repository: human-solutions/daemon-cli

Length of output: 73


🏁 Script executed:

#!/bin/bash
# Read client.rs file
wc -l src/client.rs

echo "---"

# Read the file
cat -n src/client.rs

Repository: human-solutions/daemon-cli

Length of output: 26945


🏁 Script executed:

#!/bin/bash
# Check if there's any protection against concurrent daemon spawning
rg -n 'process_exists\|kill_process' --type rust src/process.rs | head -20

echo "---"

# Look for any locking mechanisms
rg -n 'Mutex\|Lock\|RwLock\|atomic' --type rust src/server.rs src/transport.rs | head -20

echo "---"

# Check if socket creation itself has any atomicity/locking
sed -n '95,130p' src/transport.rs

Repository: human-solutions/daemon-cli

Length of output: 1269


🏁 Script executed:

#!/bin/bash
# Check if ListenerOptions::create_tokio has any built-in protection
rg -n 'create_tokio\|ListenerOptions' --type rust src/transport.rs

echo "---"

# Check if there's any documentation about daemon exclusivity
grep -r "single.*daemon\|one.*daemon\|exclusive\|concurrent.*daemon" --include="*.rs" --include="*.md" src/ . 2>/dev/null | head -10

Repository: human-solutions/daemon-cli

Length of output: 890


Based on my comprehensive verification, I can now provide the rewritten review comment.


The socket cleanup race condition is confirmed—add daemon state validation before socket removal.

The code removes an existing socket file without checking whether a daemon holding it is actually running. If two daemon processes attempt to start concurrently (e.g., multiple clients simultaneously detect no running daemon), both will call SocketServer::new(), creating a TOCTOU (time-of-check-time-of-use) race:

  1. Process A checks socket exists, removes it
  2. Process B checks socket exists (may already be removed), removes it (no-op)
  3. Process A creates listener
  4. Process B creates listener (fails or causes socket state issues)

Recommended fix: Before removing the socket at line 104, verify that the daemon process recorded in the PID file is not actually running. The PID file is already being written after successful socket creation (lines 193-198 in src/server.rs), so check for and validate it before socket cleanup. Alternatively, handle socket creation failures gracefully or add a lock file to prevent concurrent daemon initialization.

🤖 Prompt for AI Agents
In src/transport.rs around lines 100 to 109, the code unconditionally removes an
existing socket file which creates a TOCTOU race when multiple daemons start;
before calling fs::remove_file(&sock_path) verify the daemon PID from the PID
file (as written in src/server.rs lines ~193-198) and only remove the socket if
that PID is absent or the process is not running; if the PID indicates a live
daemon, abort startup with a clear error; if the PID file is missing or stale,
remove the socket and proceed, but also catch and handle listener creation
failures (retry or surface a clear error) or implement a startup lock file to
prevent concurrent initializations.

- Fix collapsible if statement in client.rs
- Use inspect_err instead of map_err in client.rs
- Use assert! instead of assert_eq! with bool literal
- Remove redundant trim() before split_whitespace()
- Use .first() instead of .get(0)
- Fix redundant guards in match statements
- Apply cargo fmt fixes

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

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@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: 2

♻️ Duplicate comments (1)
tests/version_tests.rs (1)

467-472: Test is incomplete — second command is never sent.

The test name test_multiple_commands_same_connection suggests testing multiple commands, but only one command is sent. Either complete the test by sending a second command and verifying the expected behavior (connection closed after first command), or rename the test to test_single_command_after_handshake.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7154f1 and 6495e34.

📒 Files selected for processing (8)
  • examples/common/mod.rs (1 hunks)
  • examples/concurrent.rs (1 hunks)
  • src/client.rs (8 hunks)
  • src/process.rs (1 hunks)
  • src/tests.rs (2 hunks)
  • src/transport.rs (5 hunks)
  • tests/integration_tests.rs (3 hunks)
  • tests/version_tests.rs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/tests.rs
🧰 Additional context used
🧬 Code graph analysis (3)
tests/version_tests.rs (3)
src/server.rs (1)
  • new_with_name_and_timestamp (140-162)
src/client.rs (1)
  • connect (59-71)
src/transport.rs (1)
  • connect (149-166)
src/client.rs (2)
src/process.rs (6)
  • process_exists (20-22)
  • process_exists (52-59)
  • process_exists (127-136)
  • terminate_process (32-34)
  • terminate_process (61-110)
  • terminate_process (138-169)
src/transport.rs (3)
  • daemon_socket_exists (76-87)
  • socket_path (43-47)
  • socket_path (138-140)
tests/integration_tests.rs (3)
src/lib.rs (1)
  • handle (272-278)
src/client.rs (1)
  • connect_with_name_and_timestamp (81-192)
src/transport.rs (3)
  • socket_path (43-47)
  • socket_path (138-140)
  • pid_path (50-54)
🪛 GitHub Actions: CI
src/process.rs

[error] 3-3: unused import: bail


[error] 4-4: unused imports: Duration and sleep

src/transport.rs

[error] 15-15: unused import: fs

🔇 Additional comments (19)
examples/common/mod.rs (1)

33-35: LGTM!

The changes are idiomatic improvements:

  • split_whitespace() already skips leading/trailing whitespace, making .trim() redundant.
  • parts.first() is more idiomatic than parts.get(0).
examples/concurrent.rs (1)

86-88: LGTM!

Consistent with the refactoring in examples/common/mod.rs — both improvements are idiomatic.

src/process.rs (3)

45-116: Unix implementation looks correct.

The SIGTERM → poll → SIGKILL escalation is well-structured. The handling of EPERM (process exists but owned by another user) and ESRCH (process doesn't exist) is correct.


118-178: Windows implementation is correct.

Handle management is proper with CloseHandle called in all code paths. The 5-second wait for process exit after TerminateProcess is reasonable.


180-316: Tests are comprehensive with good platform coverage.

The zombie process handling documentation (lines 233-247) is helpful for understanding expected behavior. The test assertions correctly account for the zombie edge case in Unix.

src/client.rs (4)

2-4: Imports are well-organized for cross-platform support.

The new imports from process and transport modules properly abstract platform-specific logic.


105-109: Good use of platform-aware socket existence check.

Using daemon_socket_exists instead of direct file existence check properly handles the Windows case where named pipes can't be checked via filesystem.


333-395: Clean cross-platform implementation of force_stop.

The refactoring properly uses the platform-abstracted process_exists and terminate_process APIs. Error handling with TerminateResult is clear and provides informative error messages.


499-520: Let-if chain syntax is fully supported in this project.

The project uses Rust 2024 edition (as confirmed in Cargo.toml), which natively supports let-if chains. The auto-restart logic on lines 499-520 is correctly structured and requires no changes. The condition check is clean and combines error detection, auto-restart enablement, and fatal error classification appropriately. The single retry after restart prevents infinite loops.

src/transport.rs (4)

68-87: Platform-aware socket existence checking is well-designed.

The approach of using PID file as a proxy on Windows (since named pipes can't be checked via filesystem) is appropriate and documented.


96-131: Socket server creation with platform-specific listeners looks good.

The socket cleanup on Unix (lines 103-105) is coordinated by the client's cleanup_stale_processes which checks the PID file first, mitigating the TOCTOU concern raised in past reviews.


148-166: Platform-specific connection handling is correct.

The client connection logic properly mirrors the server's platform-specific naming conventions.


216-229: LGTM!

Formatting changes to enum variants are cosmetic and improve readability.

tests/version_tests.rs (1)

347-400: LGTM!

This test properly validates that the server reports its own timestamp on version mismatch, allowing the client to detect and handle the mismatch.

tests/integration_tests.rs (5)

45-68: LGTM!

The start_test_daemon_with_limit helper properly exposes the connection limit parameter for testing connection limiting behavior.


774-905: Well-designed test handlers for edge cases.

  • ImmediateOutputHandler targets the output buffering race condition
  • LargeOutputHandler validates streaming of significant data volumes
  • PanicHandler verifies server resilience after handler panics

958-999: Good coverage for stale file cleanup.

The test properly simulates stale socket and PID files with a non-existent PID, then verifies the daemon cleans them up and starts successfully.


1001-1071: Good stress test for connection stability.

The 40/50 success threshold (80%) is reasonable for stress testing, and the final connection verification ensures server stability after stress.


1170-1197: Good security test for Unix socket permissions.

Verifying 0600 permissions ensures the socket is only accessible by the owner, which is important for daemon security.

Add #[cfg(unix)] to imports only used on Unix:
- bail, Duration, sleep in process.rs
- fs in transport.rs

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

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@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: 3

♻️ Duplicate comments (1)
src/transport.rs (1)

102-111: Note: Socket cleanup race condition previously flagged.

The socket removal at lines 105-106 without PID validation has already been flagged in previous review comments. See the existing comment for detailed analysis and recommended fixes.

🧹 Nitpick comments (1)
src/process.rs (1)

164-164: Consider using graceful_timeout_ms for wait duration.

The hardcoded 5000ms wait is inconsistent with the function's graceful_timeout_ms parameter. While Windows cannot perform graceful shutdown (as documented), the wait duration could still honor the caller's timeout preference for API consistency.

-let wait = unsafe { WaitForSingleObject(handle, 5000) };
+let wait = unsafe { WaitForSingleObject(handle, graceful_timeout_ms as u32) };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6495e34 and 201f2d9.

📒 Files selected for processing (2)
  • src/process.rs (1 hunks)
  • src/transport.rs (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (macos-latest)
  • GitHub Check: test (windows-latest)
  • GitHub Check: test (ubuntu-latest)
🔇 Additional comments (10)
src/process.rs (2)

3-7: LGTM! Platform-specific imports correctly scoped.

The unused import warnings flagged in previous reviews have been resolved by properly scoping bail, Duration, and sleep with #[cfg(unix)] attributes.


183-355: LGTM! Comprehensive test coverage with well-documented platform quirks.

The tests appropriately cover:

  • Existence checks for current and nonexistent processes
  • Termination of nonexistent processes
  • Actual process termination on both Unix and Windows
  • SIGTERM→SIGKILL escalation on Unix

The zombie process behavior in Unix tests (lines 236-241, 295-296) is correctly identified as a testing artifact, not a production issue, since daemon processes are reaped by init/systemd.

src/transport.rs (8)

4-11: LGTM: Platform-specific imports are correctly structured.

The conditional imports using #[cfg(unix)] and #[cfg(windows)] properly separate platform-specific types from the interprocess crate.


13-14: LGTM: The fs import is now Unix-only.

This change addresses the previous comment about unused imports on Windows. The fs module is now only imported on Unix where it's actually used (lines 106, 126).


70-75: LGTM: Windows socket name helper is correctly structured.

The allow(dead_code) attribute on Unix is appropriate since this function is only needed for Windows named pipe naming.


122-127: LGTM: Unix socket permissions correctly restrict access.

Setting permissions to 0600 ensures only the owner can read/write to the socket, which is appropriate for daemon IPC.


153-163: LGTM: Client connection logic is correctly implemented for both platforms.

The platform-specific connection paths appropriately mirror the server-side listener creation, using filesystem paths on Unix and named pipe names on Windows.


189-193: LGTM: Generic Stream abstraction enables clean cross-platform support.

Using the generic Stream type from the interprocess crate provides appropriate platform abstraction while maintaining the same framing semantics with LengthDelimitedCodec.


220-230: LGTM: Message enum formatting is consistent.

The formatting changes improve readability of the struct variants without altering semantics.


77-89: PID file coordination is well-implemented; no issues found.

The code shows proper sequencing: SocketServer::new() is called first (line 191 of src/server.rs), then the PID file is written immediately after (line 196), with cleanup handled via a scopeguard that removes the PID file on exit (lines 201-204). This minimizes the race window between socket creation and PID file write. The Windows approach using the PID file as a proxy is pragmatic and the synchronization is sound.

akesson and others added 5 commits November 28, 2025 09:07
Use ceiling division so any positive timeout performs at least one
iteration before escalating to SIGKILL.

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

Co-Authored-By: Claude <noreply@anthropic.com>
OpenProcess returns null on failure, not INVALID_HANDLE_VALUE.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Apply SDDL security descriptor D:P(A;;GA;;;OW) to restrict pipe access
to the owner only, equivalent to Unix 0o600 socket permissions.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Use GetExitCodeProcess to verify process is still running, not just
that OpenProcess succeeds. Fixes test_terminate_spawned_process_windows
which was failing because OpenProcess can succeed on terminated
processes during kernel cleanup.

Also fix clippy warning by using div_ceil().

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

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@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

♻️ Duplicate comments (2)
src/transport.rs (2)

1-1: Pipeline still fails on cargo fmt—run locally to fix import formatting.

Despite being marked as addressed in a previous commit, the CI pipeline continues to fail with cargo fmt --check. The error indicates cfg(windows) imports need reordering. Run cargo fmt locally and commit the formatted result.

#!/bin/bash
# Verify the exact formatting issue
cargo fmt --check 2>&1 || echo "Formatting check failed - run 'cargo fmt' to fix"

110-112: TOCTOU race condition remains unresolved—validate daemon state before socket removal.

The unconditional socket file removal still creates a race when multiple processes attempt to start the daemon simultaneously. This issue was flagged in a previous review but has not been addressed.

🧹 Nitpick comments (3)
src/process.rs (3)

9-20: Consider deriving PartialEq for easier test assertions.

The enum is well-designed. Deriving PartialEq would allow using assert_eq! directly instead of matches! in tests, though this is optional.

-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
 pub enum TerminateResult {

55-56: Potential overflow when casting pid: u32 to i32.

On Linux, PIDs are typically limited to values well under i32::MAX (default max is ~32768, configurable up to ~4 million), so this cast is safe in practice. However, an explicit check or try_into() with error handling would be more defensive.

 pub fn process_exists(pid: u32) -> Result<bool> {
+    let raw_pid: i32 = pid.try_into().map_err(|_| anyhow::anyhow!("PID {} exceeds i32::MAX", pid))?;
-    match kill(Pid::from_raw(pid as i32), None) {
+    match kill(Pid::from_raw(raw_pid), None) {

174-182: Consider including GetLastError details in error messages.

When WaitForSingleObject fails or times out, the error message could be more informative by including the wait result or last error code.

 // Wait for process to exit (up to 5 seconds)
 let wait = unsafe { WaitForSingleObject(handle, 5000) };
 unsafe { CloseHandle(handle) };

 if wait == WAIT_OBJECT_0 {
     TerminateResult::Terminated
 } else {
-    TerminateResult::Error("Process did not terminate".to_string())
+    TerminateResult::Error(format!("Process did not terminate (wait result: {})", wait))
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 201f2d9 and 065c290.

📒 Files selected for processing (3)
  • Cargo.toml (1 hunks)
  • src/process.rs (1 hunks)
  • src/transport.rs (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Cargo.toml
🧰 Additional context used
🧬 Code graph analysis (1)
src/transport.rs (2)
src/server.rs (2)
  • new (115-119)
  • e (435-435)
src/client.rs (1)
  • connect (59-71)
🪛 GitHub Actions: CI
src/process.rs

[error] 147-147: mismatched types: expected u32, found i32 in expression Ok(exit_code == STILL_ACTIVE)


[error] 147-147: cannot compare u32 with i32; PartialEq<i32> is not implemented for u32

src/transport.rs

[error] 1-1: cargo fmt --check failed. Code is not properly formatted in transport.rs. Run 'cargo fmt' (or 'cargo fmt --check' after formatting) to fix formatting issues. The diff shows reordering of cfg(windows) imports.

🔇 Additional comments (11)
src/process.rs (7)

1-7: LGTM!

The conditional compilation attributes correctly scope Unix-only imports (bail, Duration, sleep) to avoid unused import warnings on Windows. This addresses the previous review feedback.


22-46: LGTM!

The public API is clean with good documentation explaining platform-specific behavior differences. The delegation to platform modules keeps the abstraction clean.


84-94: Good fix for the edge case with small timeouts.

Using div_ceil(100) ensures at least 1 iteration for any positive timeout value, addressing the previous review feedback about timeouts below 100ms.


152-152: Unused parameter _graceful_timeout_ms is appropriately documented.

The documentation at lines 33-34 correctly explains that Windows has no SIGTERM equivalent, making the graceful timeout parameter unused. The underscore prefix correctly suppresses the unused warning.


194-227: LGTM!

The basic tests cover key scenarios: current process existence, nonexistent process handling, and graceful degradation for kill operations. Using a very high PID (4 billion) is a reasonable approach for testing nonexistent process handling.


228-329: Well-documented zombie process handling in tests.

The comments explaining why the test accepts TerminateResult::Error(_) due to zombie processes are valuable. This correctly documents expected behavior when terminating child processes directly (vs. detached daemons reaped by init).


331-365: LGTM!

The Windows test correctly expects TerminateResult::Terminated since Windows doesn't have the zombie process issue that Unix has with direct child processes.

src/transport.rs (4)

124-127: Excellent—Windows security descriptor properly restricts access to owner only.

The implementation now creates a security descriptor using SDDL "D:P(A;;GA;;;OW)" (DACL Protected, Allow Generic All to Owner), which correctly restricts the named pipe to the daemon owner, matching the Unix socket behavior (0o600). This addresses the critical security concern raised in the previous review.


82-94: Good approach using PID file as proxy for Windows named pipe existence.

Since Windows named pipes cannot be checked via the filesystem like Unix sockets, using the PID file as a proxy for daemon existence is a reasonable cross-platform solution.


165-177: LGTM—platform-specific connection logic is clean and correct.

The conditional compilation cleanly separates Unix domain socket connection from Windows named pipe connection, using the appropriate naming functions for each platform.


120-120: Widestring dependency is properly declared.

The widestring crate is correctly specified in Cargo.toml (line 31: widestring = "1.0"), so the import of u16cstr on line 120 is valid and the security descriptor implementation is properly supported.

akesson and others added 5 commits November 28, 2025 09:21
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Actually send a second command and verify the connection is closed,
confirming the server's one-shot semantics.

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

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- pre-commit: runs cargo fmt --check and clippy
- pre-push: runs cargo nextest

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

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@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: 0

🧹 Nitpick comments (2)
.cargo-husky/hooks/pre-push (1)

1-4: Verify cargo-nextest installation is documented for contributors.

The pre-push hook assumes cargo-nextest is installed. If a contributor doesn't have it, their push will fail with an unclear error. Consider adding a check or documenting the requirement in your contributing guidelines.

Optionally, add user feedback to clarify what's running:

 #!/bin/sh
 set -e
 
+echo "Running tests with cargo nextest..."
 cargo nextest run
.cargo-husky/hooks/pre-commit (1)

1-5: Strict pre-commit checks enforce code quality.

The hook correctly runs formatting checks and linting before commits. The --check flag on cargo fmt will fail commits with formatting issues (not auto-fix), and -D warnings treats all clippy warnings as errors, maintaining high code quality standards.

Optionally, add user feedback to clarify what's running:

 #!/bin/sh
 set -e
 
+echo "Checking code formatting..."
 cargo fmt -- --check
+echo "Running clippy..."
 cargo clippy -- -D warnings
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5e2059 and b0db4d4.

📒 Files selected for processing (5)
  • .cargo-husky/hooks/pre-commit (1 hunks)
  • .cargo-husky/hooks/pre-push (1 hunks)
  • CHANGELOG.md (1 hunks)
  • Cargo.toml (2 hunks)
  • tests/version_tests.rs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/version_tests.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (windows-latest)
  • GitHub Check: test (macos-latest)
  • GitHub Check: test (ubuntu-latest)
🔇 Additional comments (5)
Cargo.toml (5)

3-3: Appropriate version bump for new Windows support.

The minor version increment from 0.5.0 to 0.6.0 correctly reflects the addition of new platform support and features.


27-31: Windows dependencies updated as recommended.

The windows-sys version has been updated to 0.61.2 as suggested in the previous review, addressing the outdated version concern. The platform-specific dependency scoping is correct.


24-24: The nix crate version 0.30.1 is valid and poses no known security concerns.

Verification confirms that version 0.30.1 of the nix crate exists on crates.io (published May 4, 2025), is not yanked, and has no known security advisories. The version is the latest in the 0.30.x series, and the crate repository is actively maintained.


40-40: Verified: cargo-husky version 1.x is available with the "user-hooks" feature.

The latest 1.x version is 1.5.0 (stable, not yanked), which explicitly supports the user-hooks feature. The configuration correctly disables default features to selectively enable only user-hooks, making it appropriate for custom git hooks in a dev-dependency.


21-21: Dependency verified: interprocess v2.2 is secure and valid.

The interprocess crate version 2.2 exists (resolves to 2.2.3, released March 2025) with no known security advisories in the RustSec database. The tokio feature is properly defined in the crate and compatible with the specified version. No issues identified.

@akesson akesson merged commit b96f713 into main Nov 28, 2025
8 checks passed
@akesson akesson deleted the win-support branch November 28, 2025 08:52
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.

2 participants