junegunn/fzf · error

invalid popup option: ${arg} (expected: [center|top|bottom|l

Error message

invalid popup option: ${arg} (expected: [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]][,border-native])

What it means

Returned by startHttpServer (src/server.go:109) when net.Listen('tcp', host:port) fails while starting fzf's --listen TCP server. This is the standard bind failure for fzf's server mode: the address cannot be bound because another process owns the port, the port is privileged, or the host part cannot resolve/bind. The OS error is flattened into the formatted message, so check the port separately.

Source

Thrown at src/options.go:430

			}
		}
	}
	return shape
}

func defaultTmuxOptions(index int) *tmuxOptions {
	return &tmuxOptions{
		position: posCenter,
		width:    sizeSpec{50, true},
		height:   sizeSpec{50, true},
		index:    index}
}

func parseTmuxOptions(arg string, index int) (*tmuxOptions, error) {
	var err error
	opts := defaultTmuxOptions(index)
	tokens := splitRegexp.Split(arg, -1)
	errorToReturn := errors.New("invalid popup option: " + arg + " (expected: [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]][,border-native])")
	if len(tokens) == 0 || len(tokens) > 4 {
		return nil, errorToReturn
	}

	for i, token := range tokens {
		if token == "border-native" {
			tokens = append(tokens[:i], tokens[i+1:]...) // cut the 'border-native' option
			opts.border = true
			break
		}
	}

	// Defaults to 'center'
	first := "center"
	if len(tokens) > 0 {
		first = tokens[0]
	}

View on GitHub (pinned to bd4efa277b)

Solutions

  1. Find what holds the port and stop it or pick another: `ss -ltnp 'sport = :6266'` or `lsof -iTCP:6266 -sTCP:LISTEN`, then kill that process or rerun fzf with a free port.
  2. Use --listen 0 to let the kernel assign a free ephemeral port; fzf reports the chosen port via the shell integration ($FZF_PORT) so a fixed port is often unnecessary.
  3. If a low port (<1024) is required, run with elevated privileges or pick a port above net.ipv4.ip_unprivileged_port_start.
  4. Verify the host part of the address is a local IP/hostname of this machine (`ip addr`); use localhost or omit the host for loopback-only listening.
  5. Ensure FZF_API_KEY is set when binding a non-local address, otherwise you will hit the earlier 'FZF_API_KEY is required' error first.

Example fix

# before
fzf --listen 6266   # port already bound by a previous fzf

# after
# option A: kill the previous listener
kill $(lsof -t -iTCP:6266 -sTCP:LISTEN)
fzf --listen 6266

# option B: let fzf pick a free port (shell integration exports $FZF_PORT)
fzf --listen 0
Defensive patterns

Strategy: validation

Validate before calling

// Go caller: check the TCP port is free before spawning fzf
func portAvailable(host string, port int) bool {
    if port == 0 {
        return true // ephemeral, kernel picks
    }
    ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
    if err != nil {
        return false
    }
    ln.Close()
    return true
}

if !portAvailable("localhost", 6266) {
    // choose another port or use --listen 0
}

Type guard

func isTcpListenFailure(stderr string) bool {
    return strings.Contains(stderr, "failed to listen on ") &&
        !strings.Contains(stderr, ".sock")
}

Try / catch

// shell: retry on a fresh port when the fixed port is taken
fzf --listen 6266 2>err.log || {
    grep -q "failed to listen on" err.log && exec fzf --listen 0
}

Prevention

When it happens

Trigger: Running fzf --listen PORT (or host:PORT) when another process already binds that TCP port (another fzf server, a web app, anything); using a port < 1024 as a non-root user; specifying a host in the --listen address that does not correspond to a local interface (e.g. --listen 203.0.113.5:6266); or the ephemeral/accepted-connection state keeping a port reserved (SO_REUSEADDR is not set, TIME_WAIT). Note: binding a non-local host additionally requires FZF_API_KEY to be set (checked earlier at src/server.go:85-87).

Common situations: Two fzf instances configured with the same fixed --listen port in shell rc files or editor plugins; choosing a port already used by a dev server (3000, 8080...); Linux net.ipv4.ip_unprivileged_port_start restrictions for low ports; typo'd host in the listen address; containers where the requested port is already mapped by another process.

Related errors


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