{"record":{"id":"74c031051bb8a447","repo":"charmbracelet/crush","slug":"failed-to-remove-stale-server-socket-q-v","errorCode":null,"errorMessage":"failed to remove stale server socket %q: %v","messagePattern":"failed to remove stale server socket %q: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cmd/root.go","lineNumber":533,"sourceCode":"\t\tswitch {\n\t\tcase statErr == nil:\n\t\t\t// Probe the socket explicitly before the version-check\n\t\t\t// path. A stale unix socket file (the previous server\n\t\t\t// exited without cleaning up) would otherwise make\n\t\t\t// restartIfStale spin on a non-responsive endpoint; here\n\t\t\t// we detect it with a short DialTimeout and remove the\n\t\t\t// orphaned file so the normal spawn path can run.\n\t\t\tif hostURL.Scheme == \"unix\" {\n\t\t\t\tconn, dialErr := net.DialTimeout( //nolint:noctx\n\t\t\t\t\thostURL.Scheme, hostURL.Host, 200*time.Millisecond,\n\t\t\t\t)\n\t\t\t\tif dialErr == nil {\n\t\t\t\t\tconn.Close()\n\t\t\t\t} else if server.IsStaleSocketErr(dialErr) {\n\t\t\t\t\tslog.Warn(\"Stale socket detected, removing\",\n\t\t\t\t\t\t\"path\", hostURL.Host, \"error\", dialErr)\n\t\t\t\t\tif err := os.Remove(hostURL.Host); err != nil && !errors.Is(err, fs.ErrNotExist) {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to remove stale server socket %q: %v\", hostURL.Host, err)\n\t\t\t\t\t}\n\t\t\t\t\tneedsStart = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\trestarted, err := restartIfStale(cmd, hostURL)\n\t\t\tif err != nil {\n\t\t\t\tslog.Warn(\"Failed to check server version\", \"error\", err)\n\t\t\t}\n\t\t\tneedsStart = restarted || err != nil\n\t\tcase errors.Is(statErr, fs.ErrNotExist):\n\t\t\tneedsStart = true\n\t\tdefault:\n\t\t\tslog.Warn(\"Unexpected error stat'ing server socket, attempting cleanup\",\n\t\t\t\t\"path\", hostURL.Host, \"error\", statErr)\n\t\t\tif err := os.Remove(hostURL.Host); err != nil && !errors.Is(err, fs.ErrNotExist) {\n\t\t\t\treturn fmt.Errorf(\"failed to remove stale server socket %q: %v\", hostURL.Host, err)\n\t\t\t}","sourceCodeStart":515,"sourceCodeEnd":551,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/cmd/root.go#L515-L551","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif err := os.Remove(hostURL.Host); err != nil && !errors.Is(err, fs.ErrNotExist) {\n\treturn fmt.Errorf(\"failed to remove stale server socket %q: %v\", hostURL.Host, err)\n}\n// after\nif err := os.Remove(hostURL.Host); err != nil && !errors.Is(err, fs.ErrNotExist) {\n\tslog.Warn(\"Could not remove stale socket, forcing fresh start\", \"path\", hostURL.Host, \"error\", err)\n\t// fall through to needsStart instead of hard-failing the client\n}\nneedsStart = true","handlingStrategy":"validation","validationCode":"info, err := os.Lstat(socketPath)\nif err == nil && info.Mode()&os.ModeSocket == 0 {\n\t// not a socket: refuse or clean up before ensureServer runs\n\tos.RemoveAll(socketPath)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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)"],"tags":["filesystem","unix-socket","stale-socket","permissions"],"backgroundTag":"stale-socket-cleanup-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}