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 lines

View on GitHub (pinned to bd4efa277b)

Solutions

  1. 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.
  2. If the old instance is intentionally kept, start the new fzf with a different --listen socket path (or omit --listen).
  3. 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).
  4. 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

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


AI-assisted analysis of junegunn/fzf@bd4efa277b (2026-08-15). Data as JSON: /api/errors/d0a396f5a7fb717b. Report an issue: GitHub.