docker/cli · error

unable to set IO streams as raw terminal

Error message

unable to set IO streams as raw terminal: %s

What it means

Returned by setupInput (hijack.go:105) when setRawTerminal(h.streams) fails while switching the terminal into raw mode for an interactive hijacked session. Raw mode is required so that key sequences (including the detach keys) pass through unmodified.

Solutions

  1. Run from a real interactive terminal; do not pipe stdin when using -t.
  2. Drop the -t flag if you do not need a TTY (e.g. -i only).
  3. Verify the terminal is allocated; in CI use a pty wrapper if interactivity is required.

Example fix

# before (stdin is piped, no real TTY)
echo hi | docker run -it --rm alpine

# after (real TTY, or drop -t)
docker run -i --rm alpine
Defensive patterns

Strategy: validation

Validate before calling

import "github.com/moby/term"
if !term.IsTerminal(os.Stdin.Fd()) {
    return errors.New("cannot use raw mode: stdin is not a terminal")
}

Type guard

func isInteractiveTTY(f *os.File) bool {
    return term.IsTerminal(f.Fd())
}

Try / catch

// Degrade gracefully: if raw mode is unavailable, drop -t and run non-interactive.
if err != nil && strings.Contains(err.Error(), "raw terminal") {
    cmd.Args = removeFlag(cmd.Args, "-t"); /* rerun */
}

Prevention

When it happens

Trigger: The terminal cannot be placed into raw mode: stdin is not a TTY (piped), the terminal device does not support termios raw mode, or the platform terminal library returns an error.

Common situations: Running an interactive `docker run -it`/`attach`/`exec` with stdin redirected from a pipe or file, running under an environment without a controlling terminal (some CI), or a terminal that rejects raw mode.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/b2d129c16f7fe712. Report an issue: GitHub.

Appendix: source

Thrown at cli/command/container/hijack.go:105

	case err := <-detached:
		// Got a detach key sequence.
		return err
	case <-ctx.Done():
		return ctx.Err()
	}
}

func (h *hijackedIOStreamer) setupInput() (restore func(), _ error) {
	if h.inputStream == nil || !h.tty {
		// No need to setup input TTY.
		// The restore func is a nop.
		return func() {}, nil
	}
	if err := validateDetachKeys(h.detachKeys); err != nil {
		return nil, err
	}
	if err := setRawTerminal(h.streams); err != nil {
		return nil, fmt.Errorf("unable to set IO streams as raw terminal: %s", err)
	}

	// Use sync.Once so we may call restore multiple times but ensure we
	// only restore the terminal once.
	restore = sync.OnceFunc(func() {
		_ = restoreTerminal(h.streams, h.inputStream)
	})

	// Wrap the input to detect detach escape sequence.
	// Use default escape keys if an invalid sequence is given.
	escapeKeys := defaultEscapeKeys
	if h.detachKeys != "" {
		var err error
		escapeKeys, err = term.ToBytes(h.detachKeys)
		if err != nil {
			restore()
			return nil, err
		}

View on GitHub (pinned to 4f84911bfe)