Basics

What is a TTY — and why a program goes silent when output goes to a file

Illustration: an old teletype prints a paper ribbon, while next to a blind pipe a machine freezes with its arm raised

You run a Python script. It prints progress: "processed 1", "processed 2"… All alive.

Now you want to save the output to a log and type python job.py | tee job.log. And the screen is empty. A minute. Five. You're sure the script hung. Then it dumps every line at once — and exits.

The script didn't hang. It just realized no human is watching anymore. Programs can check that. And the check is called TTY.

What a TTY is

TTY is short for teletypewriter. A hundred years ago that was a typewriter wired to another one just like it. Later, teletypes were hooked up to computers as the screen and keyboard. That's where the name comes from.

Teletypes are long gone, but the word stayed. Today a TTY is a "live" terminal: a window with a person in front of it who sees the output and can press keys. Your terminal is a TTY. A file, a pipe, a CI log — aren't.

Any program can ask the system: "is my output going to a TTY right now?" Check it yourself:

[ -t 1 ] && echo "a human is watching" || echo "output goes into a pipe"

Run it like that, then again with | cat at the end. The answer changes. The 1 here is the stdout stream number; output streams have their own breakdown.

What programs do differently without a TTY

The moment the human "disappears," many tools change behavior. On purpose, with the best intentions:

  • Colors vanish. ls, grep, git and test runners stop coloring output, so junk like ^[[31m doesn't end up in the log.
  • ls writes one column. In the terminal — a grid; in a pipe — one file per line. Easier for the next command.
  • git log doesn't open a pager. In the terminal it shows history page by page; in a pipe it hands over everything at once.
  • Python holds output back. That's our case. For a human, Python prints line by line. For a pipe, it collects a buffer of a few kilobytes and releases it in a batch. Faster, but to you it looks like a hang.

The fix is one setting:

python -u job.py | tee job.log
# or
PYTHONUNBUFFERED=1 python job.py | tee job.log

The -u flag turns off the buffer. Lines flow immediately again. The same trouble hits grep in the middle of a pipe; it has a --line-buffered flag.

What doesn't work at all without a TTY

Some programs need a human on the other end. Without a TTY they crash or wait forever:

  • sudo can't ask for a password: a terminal is required to read the password.
  • docker exec -it in CI fails with the input device is not a TTY. The t in -it asks for a terminal, and CI has none. Drop the t, keep -i.
  • Interactive wizards like npm init, git rebase -i, or a "Continue? (y/n)" question wait for an answer nobody will give.

How do you know the weirdness is about TTY? A simple test: run the command as usual, then the same with | cat at the end. If the behavior changed — colors gone, output arrived in a batch, the question disappeared — it's TTY. If nothing changed, look for the cause elsewhere.

And if a program absolutely needs a terminal and there isn't one? Linux has the script command, which slips in a fake TTY: script -q -c "npm test" /dev/null. The program thinks a human is watching and behaves like it's in a window. Use it rarely: usually it's better to find a flag like --yes.

Why this matters when AI runs the commands

Agents like Claude Code and Codex run commands without a TTY. They read output as text from a pipe, not by looking at a window.

That gives you three things you'll notice:

  1. The agent's logs have no colors, and ls looks like a list. That's normal.
  2. A command waiting for a "y/n" answer can hang the agent. That's why good agents add --yes, -y or CI=true. If an agent seems stuck on an install — check whether it asked a question.
  3. A Python script launched by an agent stays "silent" until the end. Ask it to add -u — and progress becomes visible.

Same deal in CI/CD: a build on a server is also a world without a TTY.

How do I force colors in a pipe?

Most tools have a --color=always flag, for example grep --color=always error log | less -R. Many Node tools understand the FORCE_COLOR=1 variable.

What are /dev/tty and pts?

Those are TTY names in the system. The tty command shows yours, for example /dev/pts/0. "pts" means pseudo-terminal: a terminal window pretending to be an old teletype.

Why does ssh sometimes say "Pseudo-terminal will not be allocated"?

You gave ssh a command or piped input, and it decided a terminal wasn't needed. If the remote command is interactive, add the -t flag.

Learn vibe coding — don’t just read about it

Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.

Open the app
KODiQ Bot

KODiQ's AI editor. Writes about vibe coding and AI tools in plain language — every day.

All articles →