junegunn/fzf · error
failed to read %s
Error message
failed to read %s
What it means
The light TUI renderer could not read a character from the terminal: r.getch returned a non-OK result that is not cancellation, so the renderer closes itself and reports 'failed to read /dev/tty' (DefaultTtyDevice). It usually means stdin's TTY went away or a non-blocking read hit a hard error.
Source
Thrown at src/tui/light.go:352
env := os.Getenv(name)
if len(env) == 0 {
return defaultValue
}
return atoi(env, defaultValue)
}
func (r *LightRenderer) getBytes(cancellable bool) ([]byte, getCharResult, error) {
return r.getBytesInternal(cancellable, r.buffer, false)
}
func (r *LightRenderer) getBytesInternal(cancellable bool, buffer []byte, nonblock bool) ([]byte, getCharResult, error) {
c, result := r.getch(cancellable, nonblock)
if result == getCharCancelled {
return buffer, getCharCancelled, nil
}
if !nonblock && !result.ok() {
r.Close()
return nil, getCharError, errors.New("failed to read " + DefaultTtyDevice)
}
retries := 0
if c == Esc.Int() || nonblock {
retries = r.escDelay / escPollInterval
}
// A non-blocking read that found nothing has no byte to record. Recording
// one would put a NUL in the middle of a reply still being assembled.
if result.ok() {
buffer = append(buffer, byte(c))
}
pc := c
for {
c, result = r.getch(false, true)
if !result.ok() {
if retries > 0 {
retries--View on GitHub (pinned to bd4efa277b)
Solutions
- Reproduce with a real terminal; if this happens in CI/tests, provide a pty (e.g. 'script -qc "fzf" /dev/null')
- If the terminal genuinely died, this error is expected — just let fzf exit non-zero
- For scripting without interaction, drive fzf via its --filter mode instead of the interactive renderer
Example fix
# before fzf < /dev/null # no tty for interaction # after echo -e 'a\nb' | fzf --filter 'a'
Defensive patterns
Strategy: fallback
Validate before calling
# offer a non-interactive path when no usable tty exists
if [ ! -t 0 ] || [ ! -t 1 ]; then
printf '%s\n' "${ITEMS[@]}" | fzf --filter "$QUERY"
else
printf '%s\n' "${ITEMS[@]}" | fzf --query "$QUERY"
fi Prevention
- Check [ -t 0 ] && [ -t 1 ] before launching interactive fzf
- Use --filter in scripts, cron, and CI where terminals are absent
When it happens
Trigger: fzf's light renderer reads input from the TTY and the read fails: the terminal was closed while fzf was running, the pty was torn down (session leader exited), or a raw-mode read on /dev/tty returned an error (EBADF/EIO).
Common situations: Running fzf under ssh and the connection drops; terminal multiplexers killing the pane; CI harnesses or test runners that provide no real TTY then close stdin; nested programs stealing then closing the tty.
Related errors
- not a valid number: ${str}
- invalid border style (expected: rounded|sharp|bold|block|thi
- failed to open %s
- permission denied: ${path}
- invalid history file: ${e.Error()}
AI-assisted analysis of junegunn/fzf@bd4efa277b (2026-08-15).
Data as JSON: /api/errors/d9ec92847cd259ec.
Report an issue: GitHub.