Repository navigation
fix: add Windows daemon support - #163
Conversation
- Add platform-specific daemon launcher using CREATE_NEW_PROCESS_GROUP to isolate the daemon from parent console Ctrl+C events - Split syncDir into unix/windows builds: no-op on Windows since FlushFileBuffers requires write access on directory handles - Introduce lockPIDPath helper with a .pid sidecar on Windows to avoid LockFileEx byte-range lock conflict when writing the PID - Fix SQLite DSN for Windows drive letters (ensure triple-slash URI form)
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughPlatform-aware changes: Windows-specific daemon launch isolation and lock-PID sidecar handling, platform ChangesPlatform-specific daemon startup and locking
🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
pedronauck
left a comment
There was a problem hiding this comment.
Findings
[P2] SQLite Database Path Handling
- Issue: Relative SQLite database paths now resolve to filesystem-root paths.
- In
internal/store/sqlite.go:99, a leading/is unconditionally prepended if the path does not start with/. This correctly addresses Windows drive-letter paths, but it also incorrectly modifies valid relative paths on Unix-like systems—converting them into absolutefile:///URIs, which point to the root instead of remaining relative. OpenSQLiteDatabasedoes not require absolute paths. Bothglobaldb.Openandrundb.Openaccept and use caller-provided paths as-is, so this is a behavioral regression for any consumer using relative database paths.- Recommendation: The path normalization should be more narrowly targeted. Only add a leading slash when handling Windows drive-letter paths, or convert the input path to an absolute path explicitly/document this contract.
- Action item: Add regression tests covering both
relative.dband a Windows drive-letter path to ensure both scenarios are correctly handled.
- In
[P2] Windows PID Sidecar Path Usage in Tests
- Issue: The new Windows PID sidecar path is not used in current daemon startup lifecycle tests. As a result, Windows test runs could fail or miss the stale-PID scenario this PR intends to address.
- The PR introduces
lockPIDPathfor consistent PID file access; on Windows,internal/daemon/lock_pid_windows.go:10returnsdaemon.lock.pid. - However, existing lifecycle tests (e.g.
internal/daemon/boot_test.go:33,boot_test.go:74,boot_test.go:339,boot_test.go:372) still assert or manipulatepaths.LockPathdirectly. - On Windows,
Startwill use.pid, while tests interact withdaemon.lock, so Windows behavior isn't properly validated and could fail if executed. - Recommendation: Update all relevant tests to use
lockPIDPath(paths.LockPath)when checking the "persisted daemon PID" invariant. - Action item: Add a Windows CI/test leg if the PR is intended to support and close Windows daemon-side support.
- The PR introduces
Merge Recommendation
Not ready to merge. The overall direction is reasonable, and Windows compile checks are passing, but:
- The SQLite URI change is overly broad and regresses relative-path behavior.
- Windows lifecycle test coverage for the new PID path is incomplete for the claimed feature.
Address these issues before merging.
Extends coverage for sqliteDSN (Windows drive-letter path handling), ShouldRecoverSQLite, OpenSQLiteDatabase, Checkpoint, recoverSQLiteDatabase, EnsureSchema, values helpers (FormatTimestamp, ParseTimestamp, NullableString, NullString, NewID) and CloseSQLiteDatabase edge cases; updates boot_test.go to use lockPIDPath helper introduced by the Windows daemon fix.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/store/sqlite_test.go (1)
87-440: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winUse
t.Run("Should ...")naming consistently for subtests.Multiple new subtests use non-compliant names. Please rename them to the required
Should ...pattern for consistency with test standards.Suggested pattern
- t.Run("rejects empty path", func(t *testing.T) { + t.Run("Should reject empty path", func(t *testing.T) { - t.Run("opens and initializes a fresh database", func(t *testing.T) { + t.Run("Should open and initialize a fresh database", func(t *testing.T) {As per coding guidelines, "
**/*_test.go: MUST use t.Run("Should...") pattern for ALL test cases".🤖 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 `@internal/store/sqlite_test.go` around lines 87 - 440, Rename all subtest t.Run names in this file to follow the "Should ..." pattern (e.g., change "rejects empty path", "opens and initializes a fresh database", "opens without initialize callback", "reopening an existing database succeeds", "recovers from corrupt database file and returns fresh database", "returns error when initialize callback fails", "nil db is a no-op", "checkpoints a real WAL database", "nil db returns nil without panicking", "nil context returns error", "nil db does not panic", "non-nil db gets closed", "renames main file and existing companions", "succeeds when companion files are absent", "returns error for invalid SQL statement") to start with "Should ..." so they comply with the required naming convention; locate each t.Run call inside TestOpenSQLiteDatabase (top-level tests), TestCheckpoint, TestCloseSQLiteDatabaseNilCases, TestCloseQuietly, TestRecoverSQLiteDatabase, TestEnsureSchema and TestCloseSQLiteDatabase and update the string literals to the "Should ..." phrasing while keeping the rest of the description intact.
🤖 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 `@internal/store/sqlite_windows_test.go`:
- Around line 10-20: The test TestSQLiteDSNWindowsDriveLetter performs
assertions at the top level—wrap its assertions inside a t.Run subtest using the
"Should ..." naming convention (e.g., t.Run("Should convert Windows drive-letter
path to file URI", func(t *testing.T) { ... })), keeping the existing body
(path, dsn, wantPrefix and the strings.HasPrefix check) and preserving
t.Parallel() as appropriate; update the function so the call to sqliteDSN and
the t.Errorf assertion live inside that t.Run closure.
In `@internal/store/values_test.go`:
- Around line 36-37: Rename all subtest names in internal/store/values_test.go
to follow the required "Should..." pattern: update each t.Run(...) invocation
(including the ones around the table-driven cases and the specific occurrences
currently at the blocks starting with t.Run at lines shown in the review) so the
name string begins with "Should" and describes the expected behavior, e.g.,
change "valid case" to "Should handle valid case"; keep existing table-driven
tt.name values in sync or replace them with explicit "Should..." strings used in
t.Run calls for consistency across test functions like the ones hosting t.Run in
the values_test.go file.
- Around line 81-83: The test currently only checks presence of an error for
ParseTimestamp; update the test case table and assertions to verify specific
error content/type per invalid input by adding fields like wantErrContains
(string) and/or wantErrType (error or target type) to each tt entry, then
replace the generic (err != nil) check with targeted assertions using t.Fatalf
or testing/require helpers: use errors.As or errors.Is when checking types
(wantErrType) and check substring with strings.Contains(err.Error(),
wantErrContains) (or require.ErrorContains) for message content; apply this
change around the ParseTimestamp error check in values_test.go so each invalid
case asserts the expected error detail.
---
Outside diff comments:
In `@internal/store/sqlite_test.go`:
- Around line 87-440: Rename all subtest t.Run names in this file to follow the
"Should ..." pattern (e.g., change "rejects empty path", "opens and initializes
a fresh database", "opens without initialize callback", "reopening an existing
database succeeds", "recovers from corrupt database file and returns fresh
database", "returns error when initialize callback fails", "nil db is a no-op",
"checkpoints a real WAL database", "nil db returns nil without panicking", "nil
context returns error", "nil db does not panic", "non-nil db gets closed",
"renames main file and existing companions", "succeeds when companion files are
absent", "returns error for invalid SQL statement") to start with "Should ..."
so they comply with the required naming convention; locate each t.Run call
inside TestOpenSQLiteDatabase (top-level tests), TestCheckpoint,
TestCloseSQLiteDatabaseNilCases, TestCloseQuietly, TestRecoverSQLiteDatabase,
TestEnsureSchema and TestCloseSQLiteDatabase and update the string literals to
the "Should ..." phrasing while keeping the rest of the description intact.
🪄 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: 61fc9713-3626-4e3f-98b5-ad1b9047f397
📒 Files selected for processing (5)
internal/daemon/boot_test.gointernal/store/sqlite.gointernal/store/sqlite_test.gointernal/store/sqlite_windows_test.gointernal/store/values_test.go
✅ Files skipped from review due to trivial changes (1)
- internal/daemon/boot_test.go
…s assertion
- Wrap TestSQLiteDSNWindowsDriveLetter body in t.Run("Should ...") subtest
- Prefix all table-driven t.Run names with "Should " across values_test.go
- Rename standalone NewID subtests to start with "Should"
- Add errContains field to ParseTimestamp error cases to detect regressions
…274) * fix: restore Windows daemon support (PR #163 regression + new fixes) The v0.3 refactor to CompozyOS lost the Windows platform-specific files merged in PR #163 (fix: add Windows daemon support). Reproduces the original issues on Windows 11 with the v0.3.0-beta.2 release binary: Original PR #163 regressions re-fixed: - #148 daemon start fails: "another process has locked a portion of the file" (LockFileEx byte-range lock prevents writeLockPID) - #170 same, root-caused as LockFileEx conflict with in-process write - #157 daemon start fails: "invalid uri authority: C:" (sqliteDSN passes drive-letter path to url.URL which serialises as file://C:/... instead of file:///C:/...) - v0.3 also removed the sync_dir platform split — syncDir on info.go now fails on Windows because FlushFileBuffers requires a writable handle Additional Windows-only bugs discovered during validation: - CREATE_NEW_PROCESS_GROUP was missing from detached_windows.go spawnDetachedLoggedProcess. Unix uses Setpgid: true (detached_unix.go:58) but the Windows sibling forgot the equivalent, so the daemon inherits the parent CLI's console group and dies on parent exit. - procutil.StartedAt crashed with ACCESS_VIOLATION (0xc0000005) inside GetProcessTimes when passed nil pointers for exit/kernel/user times on some Windows builds. Pass valid Filetime pointers for all four outputs. Files: - internal/daemon/lock_pid_other.go (NEW): identity lockPIDPath for Unix - internal/daemon/lock_pid_windows.go (NEW): .pid sidecar for Windows - internal/daemon/sync_dir_unix.go (NEW): syncDir extracted from info.go - internal/daemon/sync_dir_windows.go (NEW): syncDir no-op for Windows - internal/daemon/lock.go: use lockPIDPath() at 3 call sites - internal/daemon/info.go: remove syncDir (moved to platform files) - internal/procutil/detached_windows.go: set CREATE_NEW_PROCESS_GROUP - internal/procutil/process_started_at_windows.go: pass valid Filetime pointers to GetProcessTimes, guard against invalid handles - internal/store/sqlite_config.go: add sqliteFilePath helper that prefixes drive-letter absolute paths with '/' so the URI serialises correctly - internal/store/sessiondb/read_only.go: same drive-letter normalisation inline (package boundary) Zero behaviour change on Unix / Linux / macOS — every fix is behind a //go:build windows tag or an identity function on non-Windows. Validated end-to-end on Windows 11 (build 26200) with go 1.26.5: - compozy daemon start succeeds - compozy daemon status reports "running" with PID + HTTP + UDS - compozy doctor -o json returns diagnostics via the daemon socket * refactor: export SqliteFilePath and reuse in sessiondb/read_only CodeRabbit review on PR #274 flagged the duplicate Windows drive-letter normalization in internal/store/sessiondb/read_only.go — the same logic was inlined that already lives in internal/store/sqlite_config.go under the unexported sqliteFilePath helper. Change: - Rename sqliteFilePath -> SqliteFilePath (exported) so sibling packages can reuse the canonical normalization. - Update internal call sites (sqliteReadOnlyDSN, sqliteDSNWithPragmas) to the new name. - In sessiondb/read_only.go, drop the inlined slash-prefix branch and call store.SqliteFilePath(path) instead. Also removes the now-unused path/filepath import. Behaviour is byte-identical: the exported helper contains exactly the same body as the previous unexported one. Windows drive-letter paths still serialize as file:///C:/... and non-absolute paths remain untouched.
Fixes #148
Fixes #157
Fixes #170
Problem
Three independent failures prevented the daemon from running on Windows — all causing a silent exit with no error shown to the user:
Changes
Impact on Unix / Linux / macOS
No behavioral change. All Windows-specific code is behind //go:build windows build tags. The lockPIDPath non-Windows implementation is an identity function (returns the same path). The SQLite fix only triggers when the path does not start with /, which never happens on Unix. The syncDir move is a pure refactor with identical logic.
Verification
make verifySummary by CodeRabbit
Chores
Tests