Repository navigation
Windows: daemon start always fails — update.lock owner is re-read through a second handle while byte-range locked (ERROR_LOCK_VIOLATION) #717
Description
CompozyOS version
compozy 0.3.0-beta.31 (direct binary). I didn't run beta.32, but the code path below is unchanged on v0.3.0-beta.32 and main @ 88534944a7.
Operating system
Windows (native) — Windows 11 (build 26200)
Area
Core / daemon · CLI · Install / update
ACP agent
Not agent-specific
Model / provider
—
Steps to reproduce
- Install the Windows binary (direct-binary install) and run
compozy install. - From a normal, non-elevated terminal, run
compozy daemon start.
What happened, and what did you expect?
daemon start fails before the daemon is launched:
error: cli: acquire daemon start mutation lock: daemon: read update lock "C:\Users\<user>\.compozy\update.lock": read C:\Users\<user>\.compozy\update.lock: The process cannot access the file because another process has locked a portion of the file.
(The original message was localized: "O processo não pode acessar o arquivo porque outro processo bloqueou parte do arquivo", which is ERROR_LOCK_VIOLATION.)
No update is running: compozy update --cancel reports nothing active. The only way we found to start the daemon was compozy daemon start --foreground --internal-child, which skips the mutation lock.
Expected: daemon start acquires the lock and starts the daemon.
Cause. AcquireUpdateLock (internal/daemon/update_lock.go) locks update.lock and then calls readUpdateLockOwner(cleanPath) and writeUpdateLockOwner(cleanPath, …), which open the same file again through a second handle. On Windows the lock is a mandatory byte-range lock (LockFileEx), and it blocks other handles even in the same process, so that second open fails with ERROR_LOCK_VIOLATION. On Linux and macOS flock is advisory, so the same code works there.
Patch we are running locally. It keeps the owner metadata in a sidecar file on Windows. Reading and writing through the already-locked handle would work too, if you'd rather keep a single file:
--- a/internal/daemon/update_lock.go
+++ b/internal/daemon/update_lock.go
@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "runtime"
"strings"
"time"
@@ -53,7 +54,13 @@ func AcquireUpdateLock(path string, owner UpdateLockOwner) (*UpdateLock, error)
}
return nil, ErrRuntimeMutationInProgress
}
- priorOwner, err := readUpdateLockOwner(cleanPath)
+ ownerPath := cleanPath
+ if runtime.GOOS == "windows" {
+ // Windows byte-range locks also block a second handle in this process.
+ // Keep ownership metadata beside the locked file instead of reopening it.
+ ownerPath += ".owner"
+ }
+ priorOwner, err := readUpdateLockOwner(ownerPath)
if err != nil {
unlockErr := fileLock.Unlock()
closeErr := fileLock.Close()
@@ -63,12 +70,12 @@ func AcquireUpdateLock(path string, owner UpdateLockOwner) (*UpdateLock, error)
if priorOwner != nil && !procutil.MatchesStartTime(priorOwner.PID, priorOwner.StartedAt) {
staleOwner = priorOwner
}
- if err := writeUpdateLockOwner(cleanPath, owner); err != nil {
+ if err := writeUpdateLockOwner(ownerPath, owner); err != nil {
unlockErr := fileLock.Unlock()
closeErr := fileLock.Close()
return nil, errors.Join(err, unlockErr, closeErr)
}
- return &UpdateLock{flock: fileLock, path: cleanPath, staleOwner: staleOwner}, nil
+ return &UpdateLock{flock: fileLock, path: ownerPath, staleOwner: staleOwner}, nil
}With this patch, compozy daemon start works normally on Windows.
Logs
error: cli: acquire daemon start mutation lock: daemon: read update lock "C:\Users\<user>\.compozy\update.lock": read C:\Users\<user>\.compozy\update.lock: The process cannot access the file because another process has locked a portion of the file.Final checks
- I searched existing issues and this is not a duplicate.
- I can reproduce this on the latest release. (Reproduced on beta.31; on beta.32 and
mainI only confirmed by reading the source.)
Diagnosed with Claude Code on my machine. The diff is the patch I'm running locally, offered as a starting point.
Metadata
Metadata
Assignees
Labels
Type
Fields
Projects
- StatusShow more project fieldsDone
Activity