junegunn/fzf · error
permission denied: ${path}
Error message
permission denied: ${path} What it means
Thrown by startHttpServer (src/server.go:96) when fzf is started with --listen using a *.sock UNIX domain socket address and another live process is already accepting connections on that socket file. fzf probes the existing socket with net.Dial('unix', ...); if the dial succeeds, a live server owns the socket, so fzf refuses to start instead of stealing it. A stale socket file (no listener) is silently removed and reused instead.
Source
Thrown at src/history.go:22
"errors"
"os"
"strings"
)
// History struct represents input history
type History struct {
path string
lines []string
modified map[int]string
maxSize int
cursor int
}
// NewHistory returns the pointer to a new History struct
func NewHistory(path string, maxSize int) (*History, error) {
fmtError := func(e error) error {
if os.IsPermission(e) {
return errors.New("permission denied: " + path)
}
return errors.New("invalid history file: " + e.Error())
}
// Read history file
data, err := os.ReadFile(path)
if err != nil {
// If it doesn't exist, check if we can create a file with the name
if os.IsNotExist(err) {
data = []byte{}
if err := os.WriteFile(path, data, 0600); err != nil {
return nil, fmtError(err)
}
} else {
return nil, fmtError(err)
}
}
// Split lines and limit the maximum number of linesView on GitHub (pinned to bd4efa277b)
Solutions
- Find and stop the process holding the socket: `lsof /path/to/fzf.sock` or `fuser /path/to/fzf.sock`, then kill it, and restart fzf.
- If the old instance is intentionally kept, start the new fzf with a different --listen socket path (or omit --listen).
- As a last resort, delete the socket file only after confirming nothing is listening: `ss -x | grep fzf.sock` shows no peer, then `rm /path/to/fzf.sock` (note fzf already removes truly dead sockets itself, so manual rm is rarely needed).
- Audit the wrapper/plugin that launches fzf to guarantee it terminates the previous instance or uses unique socket paths (e.g. $$ or mktemp-derived names).
Example fix
# before fzf --listen /tmp/fzf.sock # second instance while first is alive # after # stop the old instance first kill $(lsof -t /tmp/fzf.sock) 2>/dev/null fzf --listen /tmp/fzf.sock # or use a per-session socket in a wrapper fzf --listen "/tmp/fzf-$$.sock"
Defensive patterns
Strategy: validation
Validate before calling
// Go caller: before launching fzf with --listen sock, probe the socket
import (
"net"
"os/exec"
)
func socketFree(sockPath string) bool {
conn, err := net.Dial("unix", sockPath)
if err != nil {
return true // stale or absent socket: fzf will remove/reuse it
}
conn.Close()
return false // a live server owns it: fzf will error
}
// usage
if !socketFree("/tmp/fzf.sock") {
// kill old instance or pick another path before exec
}
_ = exec.Command("fzf", "--listen", "/tmp/fzf.sock") Type guard
// Go: detect the 'socket already in use' error from fzf's stderr
func isSocketInUse(stderr string, sockPath string) bool {
return strings.Contains(stderr, "socket already in use: "+sockPath)
} Try / catch
// fzf is a CLI; treat a non-zero exit as the 'catch' and branch on stderr
out, err := cmd.CombinedOutput()
if err != nil {
if strings.Contains(string(out), "socket already in use") {
// old instance alive: reuse it or kill it, then retry once
}
} Prevention
- Generate unique socket paths per session (PID/mktemp) in wrappers that spawn fzf --listen.
- Have integrations trap EXIT and kill the fzf server they started.
- Before launching, probe the socket with a unix dial and clean up or reuse a live server.
When it happens
Trigger: Running fzf with --listen /path/to/fzf.sock while a previous fzf instance started with the same --listen address is still alive; e.g. a second fzf in another shell, or a backgrounded fzf server that was never killed. Only triggered when the address string ends in .sock (parseListenAddress routes *.sock to the UNIX branch).
Common situations: Editor plugins or shell wrappers that spawn fzf with a fixed socket path and fail to clean up; orphaned fzf processes left by a crashed integration script; two terminal sessions sharing a hardcoded --listen sock path; a hung fzf from a previous session still attached to the socket.
Related errors
- invalid history file: ${e.Error()}
- invalid popup option: ${arg} (expected: [center|top|bottom|l
- not a valid integer: ${str}
- not a valid number: ${str}
- invalid format: ${str}
AI-assisted analysis of junegunn/fzf@bd4efa277b (2026-08-15).
Data as JSON: /api/errors/d0a396f5a7fb717b.
Report an issue: GitHub.