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

  1. Verify the path passed is a real terminal device (e.g. /dev/pts/N, /dev/tty*), not a regular file or pipe.
  2. Check the path with `isatty`/`stat -c %F` before passing it: `ls -l <path>` should show it as a character-special terminal device.
  3. If running in Docker/CI, allocate a pseudo-terminal (docker run -t, `script -q /dev/null`) so the process gets a valid TTY.
  4. 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

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


AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31). Data as JSON: /api/errors/a8bda6134cea28ef. Report an issue: GitHub.