go-delve/delve · error
%s is not a terminal
Error message
%s is not a terminal
What it means
attachProcessToTTY opens the given TTY path for read-write to use as stdin/stdout/stderr of a spawned process. After opening it verifies with isatty.IsTerminal that the file is actually a terminal; if not, it closes the handle and returns this error. It exists because Delve needs a real terminal device for process I/O redirection, not a regular file, pipe, or socket.
Source
Thrown at pkg/proc/native/proc_unix.go:20
package native
import (
"fmt"
"os"
"os/exec"
isatty "github.com/mattn/go-isatty"
)
func attachProcessToTTY(process *exec.Cmd, tty string) (*os.File, error) {
f, err := os.OpenFile(tty, os.O_RDWR, 0)
if err != nil {
return nil, err
}
if !isatty.IsTerminal(f.Fd()) {
f.Close()
return nil, fmt.Errorf("%s is not a terminal", f.Name())
}
process.Stdin = f
process.Stdout = f
process.Stderr = f
process.SysProcAttr.Setpgid = false
process.SysProcAttr.Setsid = true
process.SysProcAttr.Setctty = true
return f, nil
}
View on GitHub (pinned to a23773e6c3)
Solutions
- Verify the path passed is a real terminal device (e.g. /dev/pts/N, /dev/tty*), not a regular file or pipe.
- Check the path with `isatty`/`stat -c %F` before passing it: `ls -l <path>` should show it as a character-special terminal device.
- If running in Docker/CI, allocate a pseudo-terminal (docker run -t, `script -q /dev/null`) so the process gets a valid TTY.
- Ensure the TTY was not closed or reassigned between obtaining the path and calling the API; re-acquire a fresh TTY path.
Example fix
// before
proc, err := attachProcessToTTY("/tmp/app.log")
// after
f, _ := os.OpenFile("/tmp/app.log", os.O_WRONLY, 0)
if isatty.IsTerminal(f.Fd()) {
proc, err = attachProcessToTTY("/tmp/app.log")
} else {
proc, err = attachProcessToTTY("/dev/pts/3") // real terminal
} Defensive patterns
Strategy: validation
Validate before calling
f, err := os.OpenFile(ttyPath, os.O_RDWR, 0)
if err != nil { return err }
defer f.Close()
if !isatty.IsTerminal(f.Fd()) {
return fmt.Errorf("%s is not a terminal", ttyPath)
} Type guard
func isTTY(f *os.File) bool { return isatty.IsTerminal(f.Fd()) } Prevention
- Always stat/verify the path is a character device before passing it as a TTY.
- In CI/Docker use `docker run -t` or `script -q -c ... /dev/null` to allocate a pty.
- Never pass regular files or pipes where a terminal is required.
When it happens
Trigger: Calling the attach/launch path that redirects process I/O to a TTY (attachProcessToTTY) with a path that opens successfully but is not a terminal device — e.g. a regular file, FIFO, /dev/null, or a socket.
Common situations: Passing a log file or output redirection target where a TTY path is expected; using a pty slave path that was closed or replaced; running under environments that provide pipes instead of terminal devices (CI runners, docker without -t); misconfigured `--tty` style options pointing at non-terminal files.
Related errors
- Prompt for input failed.
- waiting for target execve failed: %s
- no eBPF program loaded
- eBPF map not loaded
- number of loaded libraries exceeds maximum
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/a8bda6134cea28ef.
Report an issue: GitHub.