Remove global socket fallback, enforce local-only daemons

- Remove ~/.beads/bd.sock fallback in getSocketPath()
- Always return local socket path (.beads/bd.sock)
- Add migration warning if old global socket exists
- Update AGENTS.md to remove global daemon references
- Document breaking change in CHANGELOG.md
- Fix test isolation: tests now use temp .beads dir and chdir

Prevents cross-project daemon connections and database pollution.
Each project must use its own local daemon.

Amp-Thread-ID: https://ampcode.com/threads/T-c4454192-39c6-4c67-96a9-675cbfc4db92
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-10-21 20:35:28 -07:00
parent 3e44951f15
commit e1a445afd2
5 changed files with 205 additions and 43 deletions

View File

@@ -187,12 +187,12 @@ func TestGetSocketPath(t *testing.T) {
}
})
t.Run("falls back to global socket", func(t *testing.T) {
t.Run("always returns local socket path", func(t *testing.T) {
// Ensure no local socket exists
localSocket := filepath.Join(beadsDir, "bd.sock")
os.Remove(localSocket)
// Create global socket
// Even with global socket present, should return local socket
home, err := os.UserHomeDir()
if err != nil {
t.Skip("Cannot get home directory")
@@ -208,9 +208,10 @@ func TestGetSocketPath(t *testing.T) {
}
defer os.Remove(globalSocket)
// Capture stderr to verify warning is displayed
socketPath := getSocketPath()
if socketPath != globalSocket {
t.Errorf("Expected global socket %s, got %s", globalSocket, socketPath)
if socketPath != localSocket {
t.Errorf("Expected local socket %s, got %s", localSocket, socketPath)
}
})

View File

@@ -170,6 +170,12 @@ var rootCmd = &cobra.Command{
// Attempt daemon connection
client, err := rpc.TryConnect(socketPath)
if err == nil && client != nil {
// Set expected database path for validation
if dbPath != "" {
absDBPath, _ := filepath.Abs(dbPath)
client.SetDatabasePath(absDBPath)
}
// Perform health check
health, healthErr := client.Health()
if healthErr == nil && health.Status == "healthy" {
@@ -222,6 +228,12 @@ var rootCmd = &cobra.Command{
// Retry connection after auto-start
client, err := rpc.TryConnect(socketPath)
if err == nil && client != nil {
// Set expected database path for validation
if dbPath != "" {
absDBPath, _ := filepath.Abs(dbPath)
client.SetDatabasePath(absDBPath)
}
// Check health of auto-started daemon
health, healthErr := client.Health()
if healthErr == nil && health.Status == "healthy" {
@@ -737,23 +749,21 @@ func recordDaemonStartFailure() {
}
// getSocketPath returns the daemon socket path based on the database location
// If no local socket exists, check for global socket at ~/.beads/bd.sock
// Always returns local socket path (.beads/bd.sock relative to database)
func getSocketPath() string {
// First check local socket (same directory as database: .beads/bd.sock)
// Always use local socket (same directory as database: .beads/bd.sock)
localSocket := filepath.Join(filepath.Dir(dbPath), "bd.sock")
if _, err := os.Stat(localSocket); err == nil {
return localSocket
}
// Fall back to global socket at ~/.beads/bd.sock
// Warn if old global socket exists
if home, err := os.UserHomeDir(); err == nil {
globalSocket := filepath.Join(home, ".beads", "bd.sock")
if _, err := os.Stat(globalSocket); err == nil {
return globalSocket
fmt.Fprintf(os.Stderr, "Warning: Found old global daemon socket at %s\n", globalSocket)
fmt.Fprintf(os.Stderr, "Global sockets are deprecated. Each project now uses its own local daemon.\n")
fmt.Fprintf(os.Stderr, "To migrate: Stop the global daemon and restart with 'bd daemon' in each project.\n")
}
}
// Default to local socket even if it doesn't exist
return localSocket
}