charmbracelet/crush · error
failed to remove stale server socket %q: %v
Error message
failed to remove stale server socket %q: %v
What it means
During ensureServer, when dialing the server socket fails with a 'stale socket' error, the client attempts to delete the leftover socket file at hostURL.Host via os.Remove. If the removal fails for any reason other than the file already being gone (fs.ErrNotExist), the error is wrapped as 'failed to remove stale server socket' and aborts the connection flow.
Source
Thrown at internal/cmd/root.go:533
switch {
case statErr == nil:
// Probe the socket explicitly before the version-check
// path. A stale unix socket file (the previous server
// exited without cleaning up) would otherwise make
// restartIfStale spin on a non-responsive endpoint; here
// we detect it with a short DialTimeout and remove the
// orphaned file so the normal spawn path can run.
if hostURL.Scheme == "unix" {
conn, dialErr := net.DialTimeout( //nolint:noctx
hostURL.Scheme, hostURL.Host, 200*time.Millisecond,
)
if dialErr == nil {
conn.Close()
} else if server.IsStaleSocketErr(dialErr) {
slog.Warn("Stale socket detected, removing",
"path", hostURL.Host, "error", dialErr)
if err := os.Remove(hostURL.Host); err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("failed to remove stale server socket %q: %v", hostURL.Host, err)
}
needsStart = true
break
}
}
restarted, err := restartIfStale(cmd, hostURL)
if err != nil {
slog.Warn("Failed to check server version", "error", err)
}
needsStart = restarted || err != nil
case errors.Is(statErr, fs.ErrNotExist):
needsStart = true
default:
slog.Warn("Unexpected error stat'ing server socket, attempting cleanup",
"path", hostURL.Host, "error", statErr)
if err := os.Remove(hostURL.Host); err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("failed to remove stale server socket %q: %v", hostURL.Host, err)
}View on GitHub (pinned to 7944b8e522)
Solutions
- Check permissions/ownership of the socket file and its parent directory and remove it manually: rm <socket-path> (use sudo if ownership differs).
- Verify the path in the error is a socket file, not a directory; if it is a directory, inspect what created it before deleting.
- Ensure the cache directory (config.GlobalCacheDir derived) is on a writable filesystem, not a read-only mount.
- Run crush as the same user that started the original server to avoid ownership mismatches.
Example fix
// before
if err := os.Remove(hostURL.Host); err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("failed to remove stale server socket %q: %v", hostURL.Host, err)
}
// after
if err := os.Remove(hostURL.Host); err != nil && !errors.Is(err, fs.ErrNotExist) {
slog.Warn("Could not remove stale socket, forcing fresh start", "path", hostURL.Host, "error", err)
// fall through to needsStart instead of hard-failing the client
}
needsStart = true Defensive patterns
Strategy: validation
Validate before calling
info, err := os.Lstat(socketPath)
if err == nil && info.Mode()&os.ModeSocket == 0 {
// not a socket: refuse or clean up before ensureServer runs
os.RemoveAll(socketPath)
} Prevention
- Always run client and server as the same user to keep socket ownership consistent
- Keep the cache dir on a local writable filesystem, never a read-only or network mount
- After a crash, manually verify the socket path is a socket (ls -la) before debugging
- Wrap os.Remove cleanup so fs.ErrNotExist is tolerated (the code already does this)
When it happens
Trigger: A dial to the unix socket fails with server.IsStaleSocketErr, then os.Remove(socketPath) returns a non-ErrNotExist error — e.g. permission denied on the socket file or its parent directory, the path is a non-empty directory instead of a socket, or the filesystem is read-only.
Common situations: A previous crush server crashed leaving a socket owned by a different user (e.g. after sudo use or a container user mismatch); the socket path sits on a read-only mount; another process recreated the path as a directory; stale sockets in a shared cache dir with mixed ownership.
Related errors
- failed to create parent directories: %w
- failed to create output file: %w
- failed to access file: %w
- failed to create parent directories: %w
- session ID is required for accessing directories outside wor
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/74c031051bb8a447.
Report an issue: GitHub.