Skip to content

refactor: split DaemonStarter into platform-aware launch strategies#373

Merged
xinhuagu merged 1 commit into
mainfrom
feat/357-windows-step2-daemon-launcher
Mar 29, 2026
Merged

refactor: split DaemonStarter into platform-aware launch strategies#373
xinhuagu merged 1 commit into
mainfrom
feat/357-windows-step2-daemon-launcher

Conversation

@xinhuagu

@xinhuagu xinhuagu commented Mar 29, 2026

Copy link
Copy Markdown
Owner

Step 2 of #357: DaemonStarter platform-aware refactoring

What changed

DaemonStarter.startDaemonProcess() split into three platform-specific launchers:

  • buildLinuxLauncher() — setsid + /bin/sh (unchanged behavior)
  • buildMacOSLauncher() — trap '' INT + exec (unchanged behavior)
  • buildWindowsLauncher() — java.exe, redirected streams, PIPE stdin

Supporting changes

  • Platform enum with detect() based on os.name
  • resolveJavaBin() — returns java.exe on Windows, java on Unix
  • quoteForShell() — single quotes (Unix) vs double quotes (Windows)

What did NOT change

  • macOS/Linux behavior is identical to before
  • No install/update/script/CI changes
  • No transport layer changes

Verification

  • All existing CLI and daemon tests pass
  • macOS confirmed: Platform.detect() → MACOS, no setsid → uses trap path

Risk

  • Low: Pure refactoring with no behavioral change on Unix
  • Windows launcher untested (no Windows CI yet — Step 4)

Impact on next steps

  • Step 3 can now implement Windows runtime bring-up against clear entry points
  • DaemonLock, AutoMemoryStore, ReadFileTool remain for Step 3

Summary by CodeRabbit

Release Notes

  • New Features
    • Added cross-platform daemon startup support for Linux, macOS, and Windows
    • Enhanced daemon process isolation on all platforms for improved stability
    • Expanded startup logging to display platform information alongside process ID and log path

Extract platform detection (Linux/macOS/Windows) and per-platform
ProcessBuilder construction into separate methods. No behavioral change
on macOS/Linux — same setsid (Linux) and trap INT (macOS) logic.

Windows launcher added: uses java.exe, double-quote escaping, redirected
streams for console detachment, PIPE for stdin (no /dev/null).

Key changes:
- Platform enum with detect() based on os.name
- resolveJavaBin(): java vs java.exe
- quoteForShell(): single quotes (Unix) vs double quotes (Windows)
- buildLinuxLauncher/buildMacOSLauncher/buildWindowsLauncher
- All existing tests pass (no behavioral change on Unix)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Mar 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Platform-aware daemon startup was implemented by adding OS detection and replacing the single Unix-oriented flow with platform-specific ProcessBuilder configurations for Linux, macOS, and Windows, each with appropriate process detachment and stream redirection logic.

Changes

Cohort / File(s) Summary
Platform-aware daemon launcher
aceclaw-cli/src/main/java/dev/aceclaw/cli/DaemonStarter.java
Refactored daemon startup to detect platform via os.name and dispatch to platform-specific ProcessBuilder configurations. Linux uses setsid (or fallback nohup), macOS uses /bin/sh -c with signal trap, and Windows launches via java.exe. All implementations include stream redirection for process detachment and stderr logging to daemon.log. Startup log expanded to include platform identifier.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Suggested labels

needs-tests


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Require Test Coverage For New Logic ❌ Error PR adds untested business logic including new Windows launcher implementation and platform detection utilities acknowledged as untested. Create DaemonStarterTest.java with comprehensive unit tests covering Platform.detect(), resolveJavaBin(), quoteForShell(), and all launcher builder methods.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: refactoring DaemonStarter to implement platform-aware launch strategies, which is the core focus of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Block Major Correctness And Security Risks ✅ Passed PR contains unquoted Java path defect causing process launch failures on Unix with spaces in paths, but errors are immediately observable with diagnostic information, not hidden failures qualifying as major/high-risk issues.
No Api Breaking Changes Without Version Bump ✅ Passed The PR contains no breaking changes to the public API. All public methods retain their signatures and only private/package-private helpers were added.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/357-windows-step2-daemon-launcher

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.

@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

🧹 Nitpick comments (2)
aceclaw-cli/src/main/java/dev/aceclaw/cli/DaemonStarter.java (2)

100-109: Consider logging a warning for unknown platforms.

When os.name doesn't match Windows or macOS patterns, the code silently falls back to LINUX. This could mask issues on unsupported platforms (e.g., FreeBSD, Solaris) where the Linux launcher might fail unexpectedly.

🔧 Suggested improvement
 static Platform detect() {
     String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
     if (os.contains("win")) return WINDOWS;
     if (os.contains("mac") || os.contains("darwin")) return MACOS;
+    if (!os.contains("linux") && !os.contains("nux")) {
+        LoggerFactory.getLogger(DaemonStarter.class)
+            .warn("Unknown OS '{}'; using Linux launcher", os);
+    }
     return LINUX;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@aceclaw-cli/src/main/java/dev/aceclaw/cli/DaemonStarter.java` around lines
100 - 109, Platform.detect() currently defaults to LINUX silently when os.name
doesn't match Windows or macOS; update detect() to emit a warning when the OS
string is unrecognized before returning LINUX. Locate the enum Platform and its
detect() method and add a log/ warning (using the project's logger or System.err
if none exists) that includes the raw System.getProperty("os.name", "") value
and a note that Linux is being assumed; keep the returned values unchanged
(WINDOWS, MACOS, LINUX) so behavior is preserved but a warning is recorded for
unsupported or unexpected platforms.

202-222: Use NUL device instead of PIPE for stdin on Windows.

Windows provides the NUL device as an equivalent to Unix /dev/null. Redirecting stdin to NUL is more explicit than using PIPE and avoids holding a pipe resource open. Since the daemon doesn't read stdin, either approach is safe, but NUL is the cleaner choice.

🔧 Optional: use NUL device
-        // Windows has no /dev/null; use Redirect.PIPE and don't write to it
-        // (the daemon doesn't read stdin, so this is safe)
-        pb.redirectInput(ProcessBuilder.Redirect.PIPE);
+        // Windows NUL device is equivalent to Unix /dev/null
+        pb.redirectInput(ProcessBuilder.Redirect.from(new File("NUL")));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@aceclaw-cli/src/main/java/dev/aceclaw/cli/DaemonStarter.java` around lines
202 - 222, Replace the stdin redirect in buildWindowsLauncher to use the Windows
NUL device instead of a PIPE: change the call that currently does
pb.redirectInput(ProcessBuilder.Redirect.PIPE) to
pb.redirectInput(ProcessBuilder.Redirect.from(new java.io.File("NUL"))) (add
import for java.io.File if needed); keep the existing output/error redirects
(including DAEMON_LOG) unchanged so the daemon is detached without holding an
unused pipe open.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@aceclaw-cli/src/main/java/dev/aceclaw/cli/DaemonStarter.java`:
- Around line 144-149: The constructed shell command in buildJavaCommand()
concatenates resolveJavaBin() without quoting, which can break if the Java path
contains spaces; update buildJavaCommand() to wrap the resolved java binary
using quoteForShell(resolveJavaBin()) (or an equivalent quoting helper) so both
the java path and the classpath are safely shell-quoted when building the final
command string.

---

Nitpick comments:
In `@aceclaw-cli/src/main/java/dev/aceclaw/cli/DaemonStarter.java`:
- Around line 100-109: Platform.detect() currently defaults to LINUX silently
when os.name doesn't match Windows or macOS; update detect() to emit a warning
when the OS string is unrecognized before returning LINUX. Locate the enum
Platform and its detect() method and add a log/ warning (using the project's
logger or System.err if none exists) that includes the raw
System.getProperty("os.name", "") value and a note that Linux is being assumed;
keep the returned values unchanged (WINDOWS, MACOS, LINUX) so behavior is
preserved but a warning is recorded for unsupported or unexpected platforms.
- Around line 202-222: Replace the stdin redirect in buildWindowsLauncher to use
the Windows NUL device instead of a PIPE: change the call that currently does
pb.redirectInput(ProcessBuilder.Redirect.PIPE) to
pb.redirectInput(ProcessBuilder.Redirect.from(new java.io.File("NUL"))) (add
import for java.io.File if needed); keep the existing output/error redirects
(including DAEMON_LOG) unchanged so the daemon is detached without holding an
unused pipe open.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f92dcb80-d95c-4d4a-b2e2-369498000561

📥 Commits

Reviewing files that changed from the base of the PR and between 08f1046 and b853919.

📒 Files selected for processing (1)
  • aceclaw-cli/src/main/java/dev/aceclaw/cli/DaemonStarter.java

Comment on lines +144 to +149
private static String buildJavaCommand() {
String classpath = System.getProperty("java.class.path");
return resolveJavaBin() + " --enable-preview -cp "
+ quoteForShell(classpath)
+ " dev.aceclaw.daemon.AceClawDaemon";
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Java binary path is not quoted; may break with spaces in path.

resolveJavaBin() is concatenated directly into the shell command without quoting. While rare on Unix, non-standard Java installations (e.g., /opt/my java/...) would break the shell command. Only classpath is quoted via quoteForShell().

🔧 Proposed fix
 private static String buildJavaCommand() {
     String classpath = System.getProperty("java.class.path");
-    return resolveJavaBin() + " --enable-preview -cp "
+    return quoteForShell(resolveJavaBin().toString()) + " --enable-preview -cp "
             + quoteForShell(classpath)
             + " dev.aceclaw.daemon.AceClawDaemon";
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static String buildJavaCommand() {
String classpath = System.getProperty("java.class.path");
return resolveJavaBin() + " --enable-preview -cp "
+ quoteForShell(classpath)
+ " dev.aceclaw.daemon.AceClawDaemon";
}
private static String buildJavaCommand() {
String classpath = System.getProperty("java.class.path");
return quoteForShell(resolveJavaBin().toString()) + " --enable-preview -cp "
quoteForShell(classpath)
" dev.aceclaw.daemon.AceClawDaemon";
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@aceclaw-cli/src/main/java/dev/aceclaw/cli/DaemonStarter.java` around lines
144 - 149, The constructed shell command in buildJavaCommand() concatenates
resolveJavaBin() without quoting, which can break if the Java path contains
spaces; update buildJavaCommand() to wrap the resolved java binary using
quoteForShell(resolveJavaBin()) (or an equivalent quoting helper) so both the
java path and the classpath are safely shell-quoted when building the final
command string.

@xinhuagu xinhuagu merged commit ff2c89c into main Mar 29, 2026
5 checks passed
@xinhuagu xinhuagu deleted the feat/357-windows-step2-daemon-launcher branch March 29, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant