fix(daemon): normalize paths for case-insensitive filesystem comparison (GH#869)

On macOS and Windows, filesystems are typically case-insensitive, so
/Users/foo/Desktop and /Users/foo/desktop refer to the same directory.
The daemon registry and discovery code was doing direct string comparison,
causing path mismatches when the casing differed.

Fix:
- Add NormalizePathForComparison() and PathsEqual() to internal/utils/path.go
- These resolve symlinks and lowercase paths on darwin/windows
- Update all workspace path comparisons in registry.go, discovery.go, and
  daemons.go to use PathsEqual()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
wolf
2026-01-03 13:22:52 -08:00
committed by Steve Yegge
parent a791991103
commit 631b067c1c
5 changed files with 194 additions and 6 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/daemon"
"github.com/steveyegge/beads/internal/utils"
)
// JSON response types for daemons commands
@@ -180,9 +181,10 @@ Sends shutdown command via RPC, with SIGTERM fallback if RPC fails.`,
os.Exit(1)
}
// Find matching daemon by workspace path or PID
// Use PathsEqual for case-insensitive comparison on macOS/Windows (GH#869)
var targetDaemon *daemon.DaemonInfo
for _, d := range daemons {
if d.WorkspacePath == target || fmt.Sprintf("%d", d.PID) == target {
if utils.PathsEqual(d.WorkspacePath, target) || fmt.Sprintf("%d", d.PID) == target {
targetDaemon = &d
break
}
@@ -232,9 +234,10 @@ Stops the daemon gracefully, then starts a new one.`,
os.Exit(1)
}
// Find the target daemon
// Use PathsEqual for case-insensitive comparison on macOS/Windows (GH#869)
var targetDaemon *daemon.DaemonInfo
for _, d := range daemons {
if d.WorkspacePath == target || fmt.Sprintf("%d", d.PID) == target {
if utils.PathsEqual(d.WorkspacePath, target) || fmt.Sprintf("%d", d.PID) == target {
targetDaemon = &d
break
}
@@ -350,9 +353,10 @@ Supports tail mode (last N lines) and follow mode (like tail -f).`,
os.Exit(1)
}
// Find matching daemon by workspace path or PID
// Use PathsEqual for case-insensitive comparison on macOS/Windows (GH#869)
var targetDaemon *daemon.DaemonInfo
for _, d := range daemons {
if d.WorkspacePath == target || fmt.Sprintf("%d", d.PID) == target {
if utils.PathsEqual(d.WorkspacePath, target) || fmt.Sprintf("%d", d.PID) == target {
targetDaemon = &d
break
}