{"record":{"id":"c1c94454b5619b37","repo":"joewalnes/websocketd","slug":"failed-to-remove-stale-socket-s-w","errorCode":null,"errorMessage":"failed to remove stale socket %s: %w","messagePattern":"failed to remove stale socket (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"main.go","lineNumber":149,"sourceCode":"// serveUnixSocket removes a stale socket file left behind by an unclean\n// shutdown (if any) and then serves on it. It only ever removes a path that\n// is actually a socket, never an arbitrary file that happens to be there.\n//\n// A socket file at the path may equally belong to a server that is still\n// running, so removing it unconditionally is not safe: unlinking a live\n// server's socket and binding a new one in its place leaves that process\n// running but permanently unreachable, with no error on either side. Probing\n// first tells the two apart — a successful dial means someone is listening, a\n// refused connection means the file is stale. Refusing to start on a live\n// socket matches what a TCP listener already does when its port is taken.\nfunc serveUnixSocket(path string, config *Config, log *libwebsocketd.LogScope) error {\n\tif info, err := os.Stat(path); err == nil && info.Mode()&os.ModeSocket != 0 {\n\t\tif conn, err := net.DialTimeout(\"unix\", path, unixSocketProbeTimeout); err == nil {\n\t\t\tconn.Close()\n\t\t\treturn fmt.Errorf(\"socket %s is already in use by a running server\", path)\n\t\t}\n\t\tif err := os.Remove(path); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to remove stale socket %s: %w\", path, err)\n\t\t}\n\t}\n\treturn serve(\"unix\", path, config, log)\n}\n\n// redirectAddress returns addr with its port replaced by redirPort. IPv6\n// literals must be split with net.SplitHostPort (which understands brackets);\n// splitting on the first colon lands inside \"[::1]:port\" and produced a\n// malformed listener address that failed to bind — and, being a listener\n// error, killed every other listener too.\nfunc redirectAddress(addr string, redirPort int) (string, error) {\n\thost, _, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot derive redirect address from %q: %w\", addr, err)\n\t}\n\treturn net.JoinHostPort(host, strconv.Itoa(redirPort)), nil\n}\n","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/joewalnes/websocketd/blob/7a8683dc7f9778dc615945aaed2a8dc77290227b/main.go#L131-L167","documentation":"When the existing socket file does NOT respond to a dial (it's stale, left by an unclean shutdown), websocketd removes it before rebinding. If os.Remove fails — permission denied on the socket's directory, read-only filesystem, or the file vanished/reappeared — startup aborts with this wrapped error.","triggerScenarios":"serveUnixSocket detects a stale socket (dial refused/timed out) then os.Remove(path) fails: process lacks write permission on the containing directory, the directory is read-only or a read-only mount, an immutable attribute is set, or a race replaces the file mid-remove.","commonSituations":"Old socket left in /var/run after a crash, now owned by a different uid; container filesystem remounted read-only; systemd-tmpfiles created the directory with restrictive modes; stale socket under a bind-mount owned by another container.","solutions":["Manually remove the stale socket as root or the owning user: `rm /run/app.sock`, then restart.","Fix directory permissions: the process needs write+execute on the directory to unlink (chmod/chown the directory, not just the socket file).","Move the socket into a directory exclusively managed by the service user (e.g. /run/websocketd with RuntimeDirectory in systemd).","Check for immutable flags (lsattr/chattr -i) or read-only mounts if removal keeps failing."],"exampleFix":"# before\nwebsocketd --unixsocket=/var/run/app.sock ./handler  # EACCES removing stale socket\n# after (systemd unit)\n[Service]\nRuntimeDirectory=websocketd\nExecStart=websocketd --unixsocket=/run/websocketd/app.sock ./handler","handlingStrategy":"retry","validationCode":"const dir = filepath.Dir(socketPath)\nif err := syscall.Access(dir, syscall.W_OK); err != nil {\n    return fmt.Errorf(\"cannot remove stale socket: no write permission on %s: %w\", dir, err)\n}","typeGuard":null,"tryCatchPattern":"if err := start(); err != nil && strings.Contains(err.Error(), \"failed to remove stale socket\") {\n    os.Remove(sock) // best-effort manual cleanup, may need elevation\n    err = start()\n}","preventionTips":["Give the service exclusive ownership of the socket directory (systemd RuntimeDirectory).","Avoid read-only or shared mounts for socket paths.","Add a pre-start cleanup step running with sufficient privileges to unlink stale sockets.","Check for immutable attributes if manual rm also fails."],"tags":["unix-socket","stale-socket","permissions","filesystem","startup"],"backgroundTag":"stale-socket-cleanup-failed","analyzedSha":"7a8683dc7f9778dc615945aaed2a8dc77290227b","analyzedAt":"2026-09-03T13:52:22.309Z","contentChangedAt":"2026-09-03T13:52:22.309Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}