TL;DR: A PTY (pseudo-terminal) is a virtual terminal — software that pretends to be a physical keyboard and screen. Programs like VS Code's terminal, Docker, SSH, and AI coding agents all use PTYs to run commands. When you see errors like "not a tty," "stdin is not a terminal," or "the input device is not a TTY," it means a program expected a terminal connection and did not get one. The fixes are almost always simple: add -it to Docker, use -t with SSH, or use non-interactive flags in CI/CD.

Why AI Coders Need to Know This

Every vibe coder hits a PTY error eventually. It is one of those things that seems completely random the first time — your code works fine locally, but fails in Docker with a cryptic message about terminals. Or your AI agent can run npm install in one environment but chokes in another.

The reason this matters more now than it used to: AI coding tools depend on PTYs constantly. When Claude Code runs a shell command, it needs a PTY. When OpenCode starts a dev server, it needs a PTY. When you run docker exec into a container to debug something, you need a PTY. These are not edge cases — this is your daily workflow.

Understanding PTYs means you can diagnose these errors in 30 seconds instead of spending 30 minutes Googling. It is the difference between knowing the fix and copy-pasting random Stack Overflow answers hoping one works.

You do not need to know how the Linux kernel implements pseudo-terminals. You do not need to understand the C source code. You need to know: what is a PTY, what breaks when one is missing, and how to get one when you need it. That is what this article covers.

What a PTY Actually Is (30-Second Version)

Back in the 1960s, people used teletypewriters (TTYs) — actual physical machines with a keyboard and a printer — to talk to computers. You typed a command, the computer processed it, and the teletypewriter printed the response on paper.

We do not use teletypewriters anymore, but the concept stuck. Your terminal app — whether it is the macOS Terminal, iTerm2, Windows Terminal, or the integrated terminal in VS Code — is a pseudo-terminal. It is software pretending to be that old physical machine.

A PTY has two sides:

  • The master side — this is the program controlling the terminal (your terminal app, VS Code, an AI agent).
  • The slave side — this is what the shell and commands see. To them, it looks like a real terminal with a real keyboard.

Think of it like a walkie-talkie. The master side is one person talking. The slave side is the other person listening and responding. The programs running inside your terminal do not know (or care) that the "terminal" is actually software. They just talk to it like it is real.

Why does this matter? Because many programs change their behavior depending on whether they are connected to a terminal. A progress bar only shows up when there is a terminal to draw it on. Color output only works with a terminal. Interactive prompts ("Are you sure? y/n") only make sense when someone is there to answer. When there is no PTY, these features get disabled — or the program crashes entirely.

Real Scenario: Docker Containers and AI Agents

Here is a scenario every vibe coder will encounter. You asked your AI to build a Node.js app, and now you want to run it in Docker. You type:

docker run my-node-app bash

Nothing happens. Or worse, you get:

the input device is not a TTY

The container started and immediately exited. You Google the error, find a Stack Overflow answer, add some flags, and it works. But you do not know why it works.

Here is the other scenario. You are using Claude Code or Codex CLI and ask it to run an interactive command inside a container:

docker exec my-container mysql -u root -p

The AI agent runs it and gets:

stdin is not a terminal

Or you are setting up a GitHub Actions workflow. Locally, your deploy script works fine. In CI, it fails:

sudo: no tty present and no askpass program specified

All three of these errors are the same problem. A program expected to be connected to a terminal — a PTY — and was not.

What AI Generated: Commands That Need a PTY

When you ask an AI to help you run containers, connect to servers, or set up dev environments, it generates commands that often assume a PTY exists. Here is a typical set of commands your AI might give you:

# AI tells you to run your container interactively
docker run -it --name my-app node:20 bash

# AI tells you to SSH into your server
ssh -t user@your-server.com "sudo systemctl restart nginx"

# AI tells you to run database migrations interactively
docker exec -it postgres-container psql -U admin -d mydb

# AI tells you to start a dev server with hot reload
npx next dev

# AI gives you a CI/CD script
#!/bin/bash
apt-get install -y curl
npm ci
npm run build

What to Tell Your AI

I'm getting a "not a tty" error when running this command.
Here's the full error message: [paste it]
Here's the context: [Docker/SSH/CI/local terminal]
What flags do I need to fix this?

Understanding Each Part

Let us break down what those PTY-related flags actually do:

docker run -it

This is actually two flags combined:

  • -i (interactive) — keeps the container's standard input (stdin) open, so you can type into it.
  • -t (tty) — allocates a PTY inside the container. This gives you a working terminal with colors, line editing, and proper formatting.

Without -i, the container ignores your keyboard. Without -t, there is no terminal — so commands that expect one (like bash, mysql, or psql) either fail or produce ugly, unformatted output.

docker exec -it

Same idea, but for a container that is already running. You are saying "connect me to this container with an interactive terminal." Without these flags, you can run one-off commands (docker exec my-container ls) but you cannot start an interactive shell session.

ssh -t

Forces SSH to allocate a PTY on the remote server, even when you are running a command directly (like ssh user@host "sudo restart nginx"). Without -t, SSH does not give you a terminal on the remote side — and sudo refuses to run because it needs a terminal to ask for your password.

DEBIAN_FRONTEND=noninteractive

This environment variable tells Debian/Ubuntu package managers: "Do not ask me any questions. Use defaults for everything." You use this in CI/CD and Dockerfiles where there is no PTY and no human to answer prompts.

script -q /dev/null

A workaround that allocates a PTY for the next command. The script command normally records terminal sessions, but you can use it as a PTY factory. Useful when nothing else works.

Common PTY Errors and Fixes

This is the section to bookmark. Every PTY error you will encounter as a vibe coder, what causes it, and exactly how to fix it.

Error: "the input device is not a TTY"

$ docker run my-app bash
the input device is not a TTY

Cause: Docker did not allocate a terminal for the container. The bash shell needs a terminal to function.

Fix:

# Add -it flags
docker run -it my-app bash

# If you're piping input (like in a script), use only -i
echo "ls -la" | docker run -i my-app bash

Error: "stdin is not a tty"

$ docker exec my-container mysql -u root -p
stdin is not a tty

Cause: The container is running, but your exec command did not request a terminal.

Fix:

# Add -it flags to docker exec
docker exec -it my-container mysql -u root -p

# If running from a script, pass the password directly
docker exec my-container mysql -u root -pMyPassword -e "SHOW DATABASES;"

Error: "sudo: no tty present and no askpass program specified"

$ ssh user@server "sudo systemctl restart nginx"
sudo: no tty present and no askpass program specified

Cause: sudo needs a terminal to prompt for your password. When you run a command directly over SSH (not an interactive session), no PTY is allocated by default.

Fix:

# Option 1: Force SSH to allocate a PTY
ssh -t user@server "sudo systemctl restart nginx"

# Option 2: Configure passwordless sudo for specific commands
# On the server, add to /etc/sudoers via visudo:
# user ALL=(ALL) NOPASSWD: /bin/systemctl restart nginx

Error: "not a terminal" or "terminal required"

$ git commit
error: cannot run editor: not a terminal

Cause: Git is trying to open your text editor for a commit message, but there is no terminal for the editor to display in. This happens in CI/CD pipelines and automated scripts.

Fix:

# Option 1: Pass the commit message inline
git commit -m "Your commit message here"

# Option 2: Set a non-interactive editor
GIT_EDITOR=true git commit --allow-empty-message

Error: "Warning: no tty" during npm/yarn install

$ docker build .
npm warn lifecycle: The node binary used for scripts is
/usr/bin/node but npm is using /usr/local/bin/node itself.
npm WARN: stdin is not a tty

Cause: npm detected that there is no interactive terminal. This is usually just a warning, not a breaking error — but it can cause issues with packages that have interactive install steps.

Fix:

# In Dockerfiles, this is expected. Suppress it:
RUN npm install --no-optional 2>&1

# Or set CI mode explicitly
ENV CI=true
RUN npm ci

Error: CI/CD Pipeline Fails With Interactive Prompts

# GitHub Actions log:
Do you want to continue? [Y/n] Abort.
Error: Process completed with exit code 1.

Cause: A command tried to ask a yes/no question, but there is no terminal and no human to answer.

Fix:

# For apt-get
sudo apt-get install -y curl wget

# For any Debian package manager
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y package-name

# For general "yes to everything" situations
yes | your-command-here

# Or pipe "yes" to a command expecting input
echo "y" | some-interactive-script.sh

What AI Gets Wrong About PTY

AI coding tools are surprisingly good at generating Docker and terminal commands. But they make consistent mistakes with PTY-related issues:

1. Including -t in CI/CD scripts. AI will generate docker run -it for a GitHub Actions workflow or a Jenkins pipeline. There is no terminal in CI. The -t flag will cause an error. In CI, use docker run -i (without -t) or just docker run with no interactive flags at all.

2. Forgetting -it in debugging commands. The AI gives you docker exec my-container bash without the -it flags. You get dropped into a non-interactive session or the command silently fails. Always ask: "Should this be docker exec -it?"

3. Not accounting for CI environments. AI generates install scripts that work on your laptop (where you have a terminal) but fail in GitHub Actions. It does not automatically add DEBIAN_FRONTEND=noninteractive or -y flags because it was trained on examples written for interactive use.

4. Over-explaining kernel internals. Ask an AI "what is a PTY?" and it will dive into master/slave file descriptors, /dev/pts, ioctl calls, and the POSIX specification. You do not need any of that to fix your Docker error. If your AI starts talking about kernel-level terminal emulation, redirect it: "Just tell me which flags to add."

What to Tell Your AI When It Over-Explains

I don't need the Unix internals. I'm getting this error:
[paste error]
Tell me the exact flag or config change to fix it.
Is this for Docker, SSH, CI/CD, or local terminal?

How to Debug PTY Issues with AI

When you hit a PTY error, here is a systematic approach that works with any AI coding tool:

Step 1: Identify the Context

PTY errors come from four places. Tell your AI which one:

  • Docker — container did not get a terminal
  • SSH — remote session does not have a terminal allocated
  • CI/CD — there is no terminal at all, period
  • AI agent — the agent's runtime environment does not have a PTY

Step 2: Copy the Exact Error

Do not paraphrase. Copy the full error message. PTY errors have specific wording that tells you exactly what is wrong:

  • "the input device is not a TTY" → Docker needs -it
  • "stdin is not a tty" → Missing -i flag or piped input
  • "no tty present" → SSH needs -t or sudo needs configuration
  • "not a terminal" → Command needs interactive mode, but there is none

Step 3: Ask for the Non-Interactive Alternative

The Debug Prompt

This command works in my local terminal but fails in [Docker/CI/SSH]:
[paste command]
Error: [paste error]

Give me:
1. The fix to run this command in a non-interactive environment
2. The flag to force a PTY if I need interactive mode
3. Whether this command even needs a terminal at all

Step 4: Test the Fix

PTY fixes are almost always one-line changes. Add a flag, set an environment variable, or restructure the command to not need interaction. If your AI gives you a five-paragraph explanation, it is overcomplicating it.

Where You See PTYs Every Day (Without Knowing It)

Once you know what a PTY is, you start seeing them everywhere:

  • VS Code / Cursor integrated terminal — every terminal tab is a PTY. The editor creates a master side; your shell runs on the slave side.
  • tmux and screen — terminal multiplexers that create PTYs for each pane and window. That is how you can have 10 shells in one terminal.
  • SSH sessions — when you SSH into a server, a PTY is created on the remote side so your local terminal can control the remote shell.
  • Claude Code and OpenCode — AI agents that run terminal commands need PTYs to handle interactive output, progress bars, and prompts.
  • Docker containers — only get a PTY when you request one with -t.
  • GitHub Actions — does not have a PTY. This is why interactive commands fail in CI/CD.

Quick Reference: PTY Flags Cheat Sheet

Situation Command Why
Interactive Docker container docker run -it image bash -t allocates PTY, -i keeps stdin open
Exec into running container docker exec -it container bash Same — need both flags for a shell
SSH with sudo ssh -t user@host "sudo cmd" -t forces PTY allocation on remote
CI/CD non-interactive install DEBIAN_FRONTEND=noninteractive apt-get install -y pkg Skips prompts when no terminal exists
Force a PTY for stubborn commands script -q /dev/null command script creates a PTY wrapper
Docker in CI (no terminal) docker run image command No -t flag — CI has no terminal to attach

What to Learn Next

Now that you understand PTYs, these related concepts will make much more sense:

  • Terminal Commands Guide — the commands you actually type into that PTY.
  • Docker CLI Basics — deeper dive into Docker flags, including when to use -it vs -d vs neither.
  • Claude Code Beginner's Guide — how AI agents use terminals (and PTYs) to run commands on your behalf.
  • What Is OpenCode? — another AI coding agent that relies on PTY access for interactive workflows.
  • What Is Git? — version control from the terminal, where PTY issues can surface during interactive rebases and merges.

Frequently Asked Questions

A PTY (pseudo-terminal) is a virtual terminal — software pretending to be a physical keyboard and screen. Programs use it to interact with your shell the same way a real terminal would. When you open a terminal tab in VS Code, Cursor, or any code editor, that is a PTY. It lets the program send commands and receive output as if a human were sitting at a keyboard.

This error means Docker did not allocate a pseudo-terminal for your container. By default, Docker runs containers without an interactive terminal. Add the -it flags to your docker run command: -i keeps stdin open and -t allocates a PTY. Example: docker run -it ubuntu bash. Without those flags, the container has no virtual terminal and interactive commands will fail.

AI coding agents run interactive commands on your behalf — things like npm install, git commit, or running dev servers. Many of these commands check whether they are connected to a real terminal before showing progress bars, prompts, or colored output. A PTY fakes that terminal connection so the AI agent can run these commands normally, just like you would in a terminal window.

TTY originally stood for teletypewriter — a physical machine from the 1960s that sent typed text over a wire. Today, TTY refers to any terminal device, real or virtual. A PTY (pseudo-TTY) is a specific type of TTY that is entirely software-based. Your terminal app, SSH session, and VS Code integrated terminal are all PTYs. In practice, you will see "tty" and "pty" used almost interchangeably in error messages.

CI/CD environments like GitHub Actions run without a terminal attached. Commands that expect interactive input will fail. The fix depends on the command: use non-interactive flags (like -y for apt-get), pipe input from echo or yes, set environment variables like CI=true or DEBIAN_FRONTEND=noninteractive, or use the script command to allocate a PTY. For Docker commands in CI, remove the -t flag since there is no terminal to attach to.