Skip to content

fix: restore Windows daemon support (PR #163 regression + new fixes) - #274

Merged
pedronauck merged 2 commits into
compozy:mainfrom
lyonzin:fix-windows-daemon-regression
Jul 29, 2026
Merged

pedronauck merged 2 commits into
compozy:mainfrom
lyonzin:fix-windows-daemon-regression

Conversation

@lyonzin

@lyonzin lyonzin commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The v0.3 refactor to CompozyOS silently lost the Windows platform-specific files merged in PR #163 (which itself closed issues #148, #157, #170). Reproducing on Windows 11 with the current beta binary:

$ compozy daemon start
error: cli: detached daemon exited before readiness: exit status 1: stderr=
  error: daemon: write daemon lock ".compozy/daemon.lock": another process
  has locked a portion of the file.

This is identical to the failure BrunoCanto reported in #148 on 2026-05-08, resolved in PR #163 on 2026-05-26, and then regressed sometime during the v0.3 refactor.

Root causes

Working through the failures end-to-end on Windows 11 (build 26200) with go 1.26.5 surfaced six distinct bugs — the four originally addressed by PR #163 plus two new Windows-only bugs that only became reachable once the daemon booted past the lock.

# Bug Root cause Original issue
1 write daemon.lock: another process has locked a portion of the file LockFileEx byte-range lock blocks writes from every handle in the same process, including the daemon's own writeLockPID. #148, #170
2 store: initialize sqlite database …: invalid uri authority: C: sqliteDSN calls filepath.ToSlash on an absolute drive-letter path and hands it to url.URL, which serialises as file://C:/... — SQLite parses C: as the network authority. #157
3 sync directory "…": Access is denied syncDir in info.go calls dir.Sync() on a read-only handle; on Windows FlushFileBuffers requires a writable handle. v0.3 regression
4 Daemon dies when parent CLI exits spawnDetachedLoggedProcess on Windows does not set CREATE_NEW_PROCESS_GROUP, so the daemon inherits the parent's console group and receives the same signals. Unix uses Setpgid: true for the same effect (detached_unix.go:58). new
5 Exception 0xc0000005 (ACCESS_VIOLATION) inside windows.GetProcessTimes procutil.StartedAt passes nil for the exit/kernel/user time outputs. Depending on the Windows build, the kernel dereferences these pointers and segfaults. new
6 store.migrations.refused stream=global reason=sum_mismatch (not fixed by this PR) Git for Windows applies core.autocrlf=true to .sql files; the embedded atlas.sum was hashed with LF line endings on the Linux CI. Any local build on Windows fails migration validation. not in this PR — needs .gitattributes policy discussion; documented under "Follow-up"

Changes

10 files, +106 / -25, all Windows-only or additive.

New files (all guarded by build tags)

  • internal/daemon/lock_pid_other.go — identity lockPIDPath() for non-Windows
  • internal/daemon/lock_pid_windows.go — returns lockPath + \".pid\" so PID persists in a sidecar, avoiding the LockFileEx conflict
  • internal/daemon/sync_dir_unix.go — extracted from info.go, preserves the existing returnErr pattern the v0.3 refactor added
  • internal/daemon/sync_dir_windows.go — no-op; NTFS journal guarantees durability after atomic rename

Modified files

  • internal/daemon/lock.go — 3 call sites route through lockPIDPath()
  • internal/daemon/info.go — drop the inline syncDir (moved to platform files)
  • internal/procutil/detached_windows.go — SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP}
  • internal/procutil/process_started_at_windows.go — pass valid Filetime pointers for all four outputs; guard against invalid handle
  • internal/store/sqlite_config.go — new sqliteFilePath helper that prefixes drive-letter absolute paths with /
  • internal/store/sessiondb/read_only.go — same drive-letter normalisation inline (package boundary makes reuse awkward for two lines)

Impact on Unix / Linux / macOS

Zero behavioural change.

  • lockPIDPath on non-Windows is return lockPath (identity)
  • sqliteFilePath prefix branch never fires on Unix because filepath.ToSlash already yields a leading /
  • sync_dir_unix.go is the exact function that used to live in info.go, just relocated
  • All new files are //go:build windows or //go:build !windows
  • All Windows-specific behaviour changes sit inside the Windows-only files

Unix regression test coverage is unchanged.

Validation

Manual end-to-end on Windows 11 (build 26200), go 1.26.5, github.com/gofrs/flock v0.13.0:

> compozy daemon start
Daemon
======
Status:                      running
PID:                         17980
Started:                     2026-07-29T13:39:11Z
Uptime:                      0s
Socket:                      C:\Users\Ailton\.compozy\daemon.sock
HTTP:                        localhost:2123
Active Sessions:             0
Total Sessions:              0
Version:                     dev
Network:                     ready
...

> compozy doctor -o json
{
  \"schema_version\": \"2026-07-16\",
  \"status\": \"error\",
  \"summary\": { \"total\": 36, ... }
}

compozy daemon start succeeds, the daemon accepts connections on the UDS socket, and compozy doctor reaches the daemon API. Diagnostics still return status: error in the summary because unrelated defaults are missing on a fresh install — that is expected for a bare compozy install and out of scope.

Cross-build GOOS=windows GOARCH=amd64 go build ./cmd/compozy compiles cleanly, and go build ./internal/daemon/... passes on Linux/macOS as well (no import churn on the Unix paths).

Full make verify was not run locally — the fork's atlas.sum mismatch (bug #6) blocks the pre-migration validate step until .gitattributes pins the SQL line endings or the sum policy is relaxed. CI on this PR will run the real Windows gate.

Follow-up (not in this PR)

The atlas.sum mismatch when building on Windows is a real papercut for any Windows contributor — the checksum is computed against LF-encoded SQL but Git for Windows checks the files out as CRLF by default. The clean fix is:

# .gitattributes
*.sql text eol=lf

I did not include it in this PR because it touches every SQL file's canonical line ending and deserves its own review. Happy to open a separate PR for it if you want.

Notes

Summary by CodeRabbit

  • Bug Fixes
    • Improved daemon PID locking across platforms, using safer PID storage to avoid Windows lock conflicts.
    • Prevented Ctrl+C from reaching detached daemon processes on Windows.
    • Improved Windows process timing retrieval with added invalid-handle validation to reduce crashes.
    • Fixed SQLite file:// DSN formatting for absolute Windows paths using consistent path normalization.
    • Enhanced persistence reliability by adding durable directory entry syncing on non-Windows platforms.

…fixes)

The v0.3 refactor to CompozyOS lost the Windows platform-specific files
merged in PR compozy#163 (fix: add Windows daemon support). Reproduces the
original issues on Windows 11 with the v0.3.0-beta.2 release binary:

Original PR compozy#163 regressions re-fixed:
- compozy#148 daemon start fails: "another process has locked a portion of the
  file" (LockFileEx byte-range lock prevents writeLockPID)
- compozy#170 same, root-caused as LockFileEx conflict with in-process write
- compozy#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
@lyonzin
lyonzin requested a review from pedronauck as a code owner July 29, 2026 13:42
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

@lyonzin is attempting to deploy a commit to the Compozy Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR splits daemon directory syncing and PID persistence by platform, isolates detached Windows processes from parent console signals, hardens Windows process-time retrieval, and normalizes filesystem paths used in SQLite file URLs.

Changes

Platform compatibility updates

Layer / File(s) Summary
Daemon locking and directory synchronization
internal/daemon/...
PID files use platform-specific paths; directory syncing is implemented for non-Windows systems and disabled on Windows.
Windows detached process handling
internal/procutil/*_windows.go
Detached processes are created in a new process group, while process handles and GetProcessTimes arguments are validated and initialized.
SQLite file URL path normalization
internal/store/...
SQLite DSN builders use normalized slash-separated paths, including the required leading slash for absolute Windows paths.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • compozy/compozy#163: Related platform-specific syncDir and daemon PID path changes, including Windows PID sidecars.

Suggested reviewers: pedronauck

🚥 Pre-merge checks | ✅ 5
✅ 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 clearly matches the main change: restoring Windows daemon support and addressing related regressions/fixes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix-windows-daemon-regression
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

// other file handles — even within the same process — from writing to the
// locked file. A separate .pid sidecar avoids that conflict.
func lockPIDPath(lockPath string) string {
return lockPath + ".pid"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Windows stale-lock test uses wrong path

When the daemon tests run on Windows, TestAcquireLockReclaimsStalePID writes the stale PID to the lock file while acquireLock now reads the .pid sidecar, causing the stale-PID assertion to fail in the Windows verification lane.

Context Used: CLAUDE.md (source)

Knowledge Base Used: Kernel and Daemon Lifecycle

@greptile-apps

greptile-apps Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR restores Windows daemon support across locking, process detachment, process-time inspection, daemon-info persistence, and SQLite path handling.

  • Stores the Windows daemon PID in a sidecar file to avoid LockFileEx conflicts.
  • Splits directory synchronization by platform and makes it a no-op on Windows.
  • Creates a new Windows process group for detached daemon processes.
  • Supplies valid output buffers when querying Windows process times.
  • Canonicalizes absolute Windows paths used in SQLite file URLs.

Confidence Score: 3/5

The PR is not yet safe to merge because the existing Windows stale-lock test remains incompatible with the new PID sidecar path and will fail in the Windows verification lane.

TestAcquireLockReclaimsStalePID writes its fixture to daemon.lock, but Windows production code now reads daemon.lock.pid, causing the stale PID assertion to receive zero instead of the seeded PID.

Files Needing Attention: internal/daemon/daemon_test.go, internal/daemon/lock_pid_windows.go

Important Files Changed

Filename Overview
internal/daemon/lock.go Routes PID persistence through a platform-specific path while retaining the original file as the singleton lock.
internal/daemon/lock_pid_windows.go Selects a .pid sidecar for Windows daemon PID persistence.
internal/daemon/sync_dir_windows.go Avoids unsupported directory-handle synchronization on Windows.
internal/procutil/detached_windows.go Starts detached Windows processes in a new process group.
internal/procutil/process_started_at_windows.go Validates the process handle and supplies all required GetProcessTimes output buffers.
internal/store/sqlite_config.go Canonicalizes absolute drive-letter paths before encoding SQLite file URLs.
internal/store/sessiondb/read_only.go Reuses the shared SQLite path normalization for read-only session databases.

Reviews (2): Last reviewed commit: "refactor: export SqliteFilePath and reus..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/store/sqlite_config.go (2)

39-43: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the platform-specific URL contract.

Cover absolute drive-letter paths, relative paths, and any supported UNC or extended-path forms, asserting the exact SQLite DSN serialization. The PR currently documents manual Windows validation but no automated coverage.

🤖 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_config.go` around lines 39 - 43, Add regression tests
for sqliteFilePath covering absolute drive-letter, relative, and supported UNC
or extended Windows paths, asserting the exact SQLite DSN serialization produced
by each case. Use platform-appropriate test handling so the contract is verified
consistently without changing sqliteFilePath itself.

31-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize Windows SQLite path normalization.

The same drive-letter conversion is implemented twice, allowing platform fixes to diverge.

  • internal/store/sqlite_config.go#L31-L45: make sqliteFilePath the shared implementation or move it to a shared internal helper.
  • internal/store/sessiondb/read_only.go#L184-L194: replace the local normalization block with that helper.
🤖 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_config.go` around lines 31 - 45, Centralize Windows
SQLite path normalization in sqliteFilePath, preserving its handling of absolute
drive-letter and relative paths. In internal/store/sqlite_config.go:31-45, keep
or move sqliteFilePath as the shared implementation; in
internal/store/sessiondb/read_only.go:184-194, remove the duplicate local
normalization and call the shared helper instead.
🤖 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/daemon/lock.go`:
- Around line 75-78: Synchronize the Windows daemon.lock.pid sidecar updates so
readLockPID cannot observe an empty or partially written value while
writeLockPID or Release modifies it. Use a dedicated short-lived sidecar lock or
an atomic replace protocol across the acquire, write, and release paths,
preserving stale-process cleanup and ErrAlreadyRunning behavior. Add coverage
for interrupted and concurrent acquire/release scenarios.

In `@internal/daemon/sync_dir_windows.go`:
- Around line 5-10: Update the Windows syncDir implementation to provide a
supported directory-flush path equivalent to the Unix behavior, or explicitly
document and test the weaker durability guarantee when syncDir remains a no-op.
Ensure WriteInfo’s success semantics and restart-after-crash behavior reflect
the selected durability contract.

---

Nitpick comments:
In `@internal/store/sqlite_config.go`:
- Around line 39-43: Add regression tests for sqliteFilePath covering absolute
drive-letter, relative, and supported UNC or extended Windows paths, asserting
the exact SQLite DSN serialization produced by each case. Use
platform-appropriate test handling so the contract is verified consistently
without changing sqliteFilePath itself.
- Around line 31-45: Centralize Windows SQLite path normalization in
sqliteFilePath, preserving its handling of absolute drive-letter and relative
paths. In internal/store/sqlite_config.go:31-45, keep or move sqliteFilePath as
the shared implementation; in internal/store/sessiondb/read_only.go:184-194,
remove the duplicate local normalization and call the shared helper instead.
🪄 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 Plus

Run ID: 3050141f-be7e-40fe-b58f-b4989911635a

📥 Commits

Reviewing files that changed from the base of the PR and between 3bccc48 and d6a6cfd.

📒 Files selected for processing (10)
  • internal/daemon/info.go
  • internal/daemon/lock.go
  • internal/daemon/lock_pid_other.go
  • internal/daemon/lock_pid_windows.go
  • internal/daemon/sync_dir_unix.go
  • internal/daemon/sync_dir_windows.go
  • internal/procutil/detached_windows.go
  • internal/procutil/process_started_at_windows.go
  • internal/store/sessiondb/read_only.go
  • internal/store/sqlite_config.go
💤 Files with no reviewable changes (1)
  • internal/daemon/info.go

Comment thread internal/daemon/lock.go
Comment on lines +75 to 78
priorPID, err := readLockPID(lockPIDPath(cleanPath))
if err != nil {
return nil, err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)lock\.go$|internal/daemon' || true

echo "== lock.go outline =="
ast-grep outline internal/daemon/lock.go --view compact || true

echo "== lock.go relevant sections =="
sed -n '1,220p' internal/daemon/lock.go | cat -n

Repository: compozy/compozy

Length of output: 30959


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== pid files =="
for f in internal/daemon/lock_pid_windows.go internal/daemon/lock_pid_other.go; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f"
  fi
done

echo "== usages of StalePID/ErrAlreadyRunning/l.Path/Release =="
rg -n "StalePID|ErrAlreadyRunning|errAlreadyRunning|Lock\\(|Release\\(|\.Path\(\)|\.Path\(\)" internal/daemon internal -g '*.go' | head -200

echo "== tests mentioning pid/writeLock/readLock/already running =="
rg -n "pid|writeLockPID|readLockPID|already running|alreadyRunning|StalePID|AcquireLock|Release|\\.pid" internal/daemon -g '*test.go' -g '*.go' | head -300

Repository: compozy/compozy

Length of output: 40251


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== daemon lock tests around stale pid and existing lock =="
sed -n '1,260p' internal/daemon/daemon_test.go | cat -n

echo "== Windows-specific references =="
rg -n "windows|\\.pid|writeLockPID|readLockPID|AcquireLock\\(" internal/daemon -g '*.go' | rg 'windows|\\.pid|writeLockPID|readLockPID|AcquireLock\\(' || true

Repository: compozy/compozy

Length of output: 10538


🌐 Web query:

Windows flock Go LockFileEx byte range file lock writes same file locked bytes

💡 Result:

On Windows, the LockFileEx API provides mandatory byte-range locking rather than advisory locking [1]. When you use LockFileEx, you are locking a specific range of bytes defined by an offset and a length [2][3]. Key behavior details: - Mandatory Locking: Unlike many POSIX systems where file locks are often advisory, Windows byte-range locks are mandatory [1]. Any process attempting to read from or write to the locked range will receive an error (typically ERROR_LOCK_VIOLATION or 33), even if that process does not use locks itself [1]. - Range Specification: The lock covers a specific byte range starting at the offset provided in the OVERLAPPED structure [2][3]. The length of the range is specified by the nNumberOfBytesToLockLow and nNumberOfBytesToLockHigh parameters [3]. To lock the entire file, you must specify a range that covers all possible bytes, often by setting these parameters to 0xFFFFFFFF (MAXDWORD) [4]. - Overlaps: Exclusive locks cannot overlap an existing locked region of a file [3]. - Go Implementation: In Go, the syscall or golang.org/x/sys/windows packages are used to invoke LockFileEx [5][6]. Standard Go library code (such as that used in the cmd/go tool) typically manages locking by passing an OVERLAPPED structure with a zero offset and a length of all bytes (represented as ^uint32(0)) to effectively lock the entire file [5]. When using these locks to manage writes, the lock prevents other processes from accessing the locked byte range [2]. If a process holds an exclusive lock on a range, it is guaranteed that no other process can perform I/O within that specific range [2][1]. If you require protection for the entire file, you should ensure the locked range encompasses all data you intend to write [4].

Citations:


Synchronize or atomically replace the Windows PID sidecar.

On Windows, the daemon PID is written to daemon.lock.pid, an unlocked sidecar. readLockPID observes that file before TryLock, while writeLockPID truncates/replaces it and Release zeroes it while holding only the main lock. A concurrent/acquired-contender or crashed release can read an empty or partially written PID, which turns a stale daemon into ErrAlreadyRunning without a PID or skips stale-process cleanup. Protect the .pid sidecar with its own short-lived lock or use an atomic update protocol, and cover interrupted/concurrent acquire and release paths.

🤖 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/daemon/lock.go` around lines 75 - 78, Synchronize the Windows
daemon.lock.pid sidecar updates so readLockPID cannot observe an empty or
partially written value while writeLockPID or Release modifies it. Use a
dedicated short-lived sidecar lock or an atomic replace protocol across the
acquire, write, and release paths, preserving stale-process cleanup and
ErrAlreadyRunning behavior. Add coverage for interrupted and concurrent
acquire/release scenarios.

Comment on lines +5 to +10
// syncDir is a no-op on Windows: FlushFileBuffers requires write access on
// directory handles, but os.Open opens them read-only. Directory entry
// durability after an atomic rename is handled by the NTFS journal.
func syncDir(_ string) error {
return nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^|/)sync_dir_windows\.go$|(^|/)sync_dir.*\.go$|internal/daemon|daemon\.json|WriteInfo' || true

echo
echo "== file excerpts =="
for f in $(git ls-files | rg '(^|/)sync_dir_.*\.go$'); do
  echo "--- $f"
  wc -l "$f"
  cat -n "$f"
done

echo
echo "== WriteInfo references =="
rg -n "\bWriteInfo\b|syncDir\(|FlushFileBuffers|os\.Open.*daemon|daemon\.json" internal app cmd website packages 2>/dev/null || true

echo
echo "== changed files summary =="
git diff --stat || true
git diff -- internal/daemon/sync_dir_windows.go internal/daemon 2>/dev/null | sed -n '1,220p' || true

Repository: compozy/compozy

Length of output: 31998


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== internal/daemon/info.go =="
cat -n internal/daemon/info.go

echo
echo "== internal/fileutil atomic directories =="
for f in internal/fileutil/atomic.go internal/fileutil/atomic_dirsync_unix.go internal/fileutil/atomic_dirsync_windows.go; do
  if [ -f "$f" ]; then
    echo "--- $f"
    wc -l "$f"
    cat -n "$f"
  fi
done

echo
echo "== daemon WriteInfo call sites =="
rg -n -C3 '\bWriteInfo\s*\(' internal/daemon | head -n 220

Repository: compozy/compozy

Length of output: 17928


🌐 Web query:

Microsoft FlushFileBuffers directory handle documentation write access requirement

💡 Result:

The FlushFileBuffers function in the Windows API requires the provided handle to have GENERIC_WRITE access rights [1][2]. This requirement applies generally to any handle passed to the function, including handles to files and volumes [1][2]. While official Microsoft documentation explicitly states that FlushFileBuffers accepts handles to files and volumes, it does not document the use of directory handles with this function [1][3]. Although developers may sometimes attempt to use directory handles obtained via CreateFile with the FILE_FLAG_BACKUP_SEMANTICS flag, the behavior of FlushFileBuffers with such handles is not an officially supported or guaranteed operation [3][4]. If you are using FlushFileBuffers for a file or volume, ensure that the handle was opened with at least GENERIC_WRITE access [1][2]. If the function is called with a handle lacking the necessary write permissions, it will fail [5].

Citations:


Treat the Windows directory flush as unsupported.

The Windows side is currently no-op, so WriteInfo can return success even though the directory metadata has not been surfaced like the Unix path does. Add a supported Windows directory flush path or document this as a weaker durability guarantee and cover restart-after-crash behavior.

🤖 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/daemon/sync_dir_windows.go` around lines 5 - 10, Update the Windows
syncDir implementation to provide a supported directory-flush path equivalent to
the Unix behavior, or explicitly document and test the weaker durability
guarantee when syncDir remains a no-op. Ensure WriteInfo’s success semantics and
restart-after-crash behavior reflect the selected durability contract.

@pedronauck

pedronauck commented Jul 29, 2026 •

Copy link
Copy Markdown
Member

@lyonzin soon as you solve the reviews issues and CI checks here I can merge this 🙏🏻 thanks for the work

CodeRabbit review on PR compozy#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.
@lyonzin

lyonzin commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the quick review @pedronauck! Update after CI feedback:

CodeRabbit review addressed

Commit 4da6e16c — refactor: export SqliteFilePath and reuse in sessiondb/read_only.

The Windows drive-letter normalization is now centralized:

  • sqliteFilePath → SqliteFilePath (exported) in internal/store/sqlite_config.go
  • Internal call sites (sqliteReadOnlyDSN, sqliteDSNWithPragmas) updated to the new name
  • internal/store/sessiondb/read_only.go drops the inlined slash-prefix branch and calls store.SqliteFilePath(path) instead; path/filepath import removed

Byte-identical behaviour — only one canonical implementation now.

⚠️ On the Go (format, lint) failure

The lint failure is not from this PR — the same failure reproduces on main (latest push cab82e43 at 15:36 UTC also completed with conclusion: failure). The specific violation:

internal/skillscan/scan.go:41:1: cyclomatic complexity 23 of func `ScanDirectory` is high (> 20) (gocyclo)
internal/skillscan/scan.go:129:1: cyclomatic complexity 22 of func `ScanFS` is high (> 20) (gocyclo)

was introduced in #270 (feat: support grouped skill directories, merged 2026-07-29 04:04 UTC). Every subsequent build hits the same threshold — nothing I touched is under internal/skillscan/.

I intentionally did not touch internal/skillscan/scan.go because that would blur two unrelated concerns in the same PR. Two options:

  1. You (or someone with authority over that surface) opens a separate PR to refactor those two funcs under the gocyclo cap, main goes green, and this PR's CI re-runs cleanly.
  2. If preferred, I can send a follow-up PR that refactors ScanDirectory / ScanFS — happy to do it, just wanted to keep this PR narrow to the Windows daemon regression.

Let me know how you'd like to proceed. Everything else (frontend, race tests, build, boundaries, preflight, react-doctor) is green.

Vercel failure

Appears to be the docs preview needing repo authorization (vercel.com/git/authorize?...&type=github) — outside my fork's reach. Should clear on your side once the deployment gets access.

Ready for merge once the shared lint issue is resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/store/sqlite_config.go (1)

31-45: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add regression tests for Windows SQLite path serialization.

Cover drive-letter paths, UNC paths, POSIX absolute paths, and relative paths, and assert the final DSNs produced by both builders—not only the helper output. This is the core Windows compatibility fix and should be exercised on a Windows test runner.

🤖 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_config.go` around lines 31 - 45, Add regression tests
covering drive-letter, UNC, POSIX absolute, and relative paths, verifying the
complete SQLite DSNs produced by both builders that use SqliteFilePath rather
than only testing the helper. Ensure the cases run correctly on Windows,
including expected file:// serialization and existing relative-path behavior.
🤖 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 `@internal/store/sqlite_config.go`:
- Around line 31-45: Add regression tests covering drive-letter, UNC, POSIX
absolute, and relative paths, verifying the complete SQLite DSNs produced by
both builders that use SqliteFilePath rather than only testing the helper.
Ensure the cases run correctly on Windows, including expected file://
serialization and existing relative-path behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d6fa59f4-6b26-4dcf-a6d7-a27eb586bb94

📥 Commits

Reviewing files that changed from the base of the PR and between d6a6cfd and 4da6e16.

📒 Files selected for processing (2)
  • internal/store/sessiondb/read_only.go
  • internal/store/sqlite_config.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/store/sessiondb/read_only.go

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