junegunn/fzf · error

failed to open %s

Error message

failed to open %s

What it means

openTty failed to obtain a usable terminal device: the candidate input is not a TTY, and fallback opens of the controlling terminal name and /dev/tty both failed. fzf needs a real terminal for its interactive UI, so this aborts before rendering.

Source

Thrown at src/tui/light_unix.go:64

func openTty(ttyDefault string, mode int) (*os.File, error) {
	var in *os.File
	var err error
	if len(ttyDefault) > 0 {
		in, err = os.OpenFile(ttyDefault, mode, 0)
	}
	if in == nil || err != nil || ttyDefault != DefaultTtyDevice && !util.IsTty(in) {
		tty := ttyname()
		if len(tty) > 0 {
			if in, err := os.OpenFile(tty, mode, 0); err == nil {
				return in, nil
			}
		}
		if ttyDefault != DefaultTtyDevice {
			if in, err = os.OpenFile(DefaultTtyDevice, mode, 0); err == nil {
				return in, nil
			}
		}
		return nil, errors.New("failed to open " + DefaultTtyDevice)
	}
	return in, nil
}

func openTtyIn(ttyDefault string) (*os.File, error) {
	return openTty(ttyDefault, syscall.O_RDONLY)
}

func openTtyOut(ttyDefault string) (*os.File, error) {
	return openTty(ttyDefault, syscall.O_WRONLY)
}

func (r *LightRenderer) setupTerminal() {
	term.MakeRaw(r.fd())
}

func (r *LightRenderer) restoreTerminal() {
	term.Restore(r.fd(), r.origState)

View on GitHub (pinned to bd4efa277b)

Solutions

  1. Allocate a TTY: run fzf in an interactive shell or use 'script -qec "fzf" /dev/null'
  2. For non-interactive filtering use 'fzf --filter QUERY' which never opens the TUI
  3. In ssh, enable allocation with 'ssh -t'

Example fix

# before (cron, no controlling terminal)
fzf
# after
printf 'a\nb\n' | fzf --filter a
Defensive patterns

Strategy: fallback

Validate before calling

# ensure a controlling terminal or fall back to filter mode
if ! [ -t 0 ] && ! [ -e /dev/tty ]; then
  exec fzf --filter "$QUERY"
fi
fzf --query "$QUERY"

Prevention

When it happens

Trigger: openTtyIn/openTtyOut are called when stdin/stdout is redirected (pipe/file) and either ttyname() yields nothing, the named tty cannot be opened, or opening DefaultTtyDevice (/dev/tty) fails — typical when the process has no controlling terminal.

Common situations: Piping into fzf inside cron jobs, CI pipelines, or daemons where there is no controlling terminal; ssh -T sessions without tty allocation; minimal containers lacking /dev/tty.

Related errors


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