From 7de4b428f378142632b8f345d781e504450289af Mon Sep 17 00:00:00 2001 From: Zach Barahal Date: Mon, 12 Jan 2026 23:06:00 -0800 Subject: [PATCH] fix: use dbPath instead of socketPath for beadsDir derivation (#1064) When using short socket paths (workspaces with paths >103 chars), filepath.Dir(socketPath) returns /tmp/beads-XXXXX/ instead of the actual .beads/ directory. This caused TryDaemonLock to look in the wrong location, always return false, clean up the startlock, and recurse infinitely causing stack overflow. Changed 4 occurrences in acquireStartLock, handleStaleLock, handleExistingSocket, and getPIDFileForSocket to use filepath.Dir(dbPath) which correctly points to .beads/. Fixes: gt-qlt Co-authored-by: furiosa Co-authored-by: Claude Opus 4.5 --- cmd/bd/daemon_autostart.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/bd/daemon_autostart.go b/cmd/bd/daemon_autostart.go index ebf66473..aff399fd 100644 --- a/cmd/bd/daemon_autostart.go +++ b/cmd/bd/daemon_autostart.go @@ -235,7 +235,7 @@ func acquireStartLock(lockPath, socketPath string) bool { // PID is alive - but is daemon actually running/starting? // Use flock-based check as authoritative source (immune to PID reuse) - beadsDir := filepath.Dir(socketPath) + beadsDir := filepath.Dir(dbPath) if running, _ := lockfile.TryDaemonLock(beadsDir); !running { // Daemon lock not held - the start attempt failed or process was reused debugLog("startlock PID %d alive but daemon lock not held, cleaning up", lockPID) @@ -267,7 +267,7 @@ func handleStaleLock(lockPath, socketPath string) bool { } // PID is alive - but check daemon lock as authoritative source (immune to PID reuse) - beadsDir := filepath.Dir(socketPath) + beadsDir := filepath.Dir(dbPath) if running, _ := lockfile.TryDaemonLock(beadsDir); !running { debugLog("lock PID %d alive but daemon lock not held, removing and retrying", lockPID) _ = os.Remove(lockPath) @@ -291,7 +291,7 @@ func handleExistingSocket(socketPath string) bool { // Use flock-based check as authoritative source (immune to PID reuse) // If daemon lock is not held, daemon is definitely dead regardless of PID file - beadsDir := filepath.Dir(socketPath) + beadsDir := filepath.Dir(dbPath) if running, pid := lockfile.TryDaemonLock(beadsDir); running { debugLog("daemon lock held (PID %d), waiting for socket", pid) return waitForSocketReadiness(socketPath, 5*time.Second) @@ -400,8 +400,8 @@ func setupDaemonIO(cmd *exec.Cmd) { // getPIDFileForSocket returns the PID file path for a given socket path func getPIDFileForSocket(socketPath string) string { - // PID file is in same directory as socket, named daemon.pid - dir := filepath.Dir(socketPath) + // PID file is in .beads directory, not socket directory (socket may be in /tmp for short paths) + dir := filepath.Dir(dbPath) return filepath.Join(dir, "daemon.pid") }