DEV Community

Cover image for Paste 2,000 Characters at a Line-Mode Prompt and macOS Keeps 1,024. Blame the TTY
Gabriel Anhaia
Gabriel Anhaia

Posted on AI-assisted

Paste 2,000 Characters at a Line-Mode Prompt and macOS Keeps 1,024. Blame the TTY


In July 2026 someone filed issue #81485 against Claude Code: on macOS, a deep link that opens a session by typing a launch command into a terminal failed without a word whenever that command was longer than 1,024 bytes. The report describes "truncation at exactly the 1024-byte boundary, command never executes, no session." The terminal bell rang. The command sat at the shell prompt, cut short, with its trailing newline gone.

iTerm2 didn't do that, and neither did Terminal.app, where the report says it also reproduces. The kernel did. Every keystroke and every paste you send to a Unix terminal goes through a layer most developers never think about, and that layer has a size limit on the line you're typing. It differs by OS. It also explains a second failure, a big paste that hangs the terminal.

The kernel edits your line before your program sees it

A terminal window talks to your program through a pseudo-terminal, a pair of kernel devices. The terminal app writes your keystrokes into one end, your program reads from the other, and in between sits the line discipline. By default it runs in canonical mode, which the Linux termios(3) man page describes plainly: "Input is made available line by line," and line editing (erase, kill) is enabled.

That's why Backspace works in cat even though cat has no editing code. The kernel holds the line in its own buffer, applies your Backspace and Ctrl-U there, and only hands the line to read() when you press Enter. Until then, the program sees nothing.

You can see the mode with stty:

stty -a | grep -o -- '-\{0,1\}icanon'
Enter fullscreen mode Exit fullscreen mode

icanon means canonical mode is on. -icanon means it's off. stty sane, the reset the man page lists, switches it back on.

The kernel's line buffer is fixed in size. Hold that thought.

1,024 bytes on macOS, 4,095 on Linux

On macOS, the limit lives in XNU's bsd/sys/syslimits.h:

#define MAX_CANON 1024 /* max bytes in term canon input line */
#define MAX_INPUT 1024 /* max bytes in terminal input */
Enter fullscreen mode Exit fullscreen mode

The code that enforces it is ttyinput() in XNU's bsd/kern/tty.c. Once the queued input reaches MAX_INPUT, each new character is thrown away, and with IMAXBEL set the kernel rings the bell. Signal keys (Ctrl-C), flow control and the erase and kill keys are handled before the overflow check. The newline and the EOF key are handled after it. So on a full line, Ctrl-C and Backspace still work, but Enter and Ctrl-D get dropped like any other character. The Claude Code command never ran because its Enter never arrived.

An Apple Developer Forums thread from 2024 shows the same thing from the user side: run base64, paste, and "I can paste or type up to 1024 characters (bytes). After that terminal starts beeping and does not allow me to type or paste anymore input." Apple's DTS engineer answered that the limit "comes from how the shell and the underlying tty manage their I/O, not terminal.app."

Linux allows a longer line and handles the overflow differently. termios(3) puts the maximum line at "4096 chars (including the terminating newline character)," and says "any input data after 4095 characters up to (but not including) any terminating newline is discarded." The buffer behind it is N_TTY_BUF_SIZE, defined as 4096 in drivers/tty/n_tty.c. Linux keeps the newline, so your truncated line still gets submitted. macOS drops it, so the line sits there.

Neither OS tells your program. It receives a shorter line and has no way to know.

Run the test yourself

Put a 5,000-character line with no newline on your clipboard. On macOS:

head -c 5000 /dev/zero | tr '\0' a | pbcopy
Enter fullscreen mode Exit fullscreen mode

On Linux, pipe the same head | tr into whatever clipboard tool your desktop uses. Now run wc -c, paste, press Enter, then Ctrl-D:

wc -c
Enter fullscreen mode Exit fullscreen mode

From the sources above, here's what to expect. On Linux, termios(3) says you keep 4,095 characters plus the newline, so wc should print 4096 instead of 5001. On macOS the bell rings after 1,024 bytes, and Enter and Ctrl-D do nothing, because they're dropped too. Press Ctrl-C to get out.

Now the same paste with canonical mode switched off for one command:

stty -icanon min 1; head -c 5000 | wc -c; stty icanon
Enter fullscreen mode Exit fullscreen mode

-icanon turns off line assembly, and min 1 (per the stty man page, "with -icanon, set N characters minimum for a completed read") makes each read() return as soon as a byte is there. head drains the bytes as they arrive, so no line ever has to fit in the kernel's buffer. If the TTY was the culprit, this one reports 5000. The Apple engineer in that forum thread describes pasting a single very long line into pico without trouble. A full-screen editor like pico reads in non-canonical mode, so the line buffer never fills.

When nobody is reading, the writer waits

Truncation needs a program that's reading. The other failure needs one that isn't: a process busy compiling, sleeping, or stuck, while you paste a few thousand lines of stack trace at it.

The bytes still have to go somewhere. On Linux they land in the line discipline's 4,096-byte read buffer, and once that fills, the line discipline stops accepting data and the bytes back up in the pty's flip buffers. The Linux pty driver has a pty_write_room() that reports how much space is left on the other side, and the flip buffers behind it have a byte cap, TTYB_DEFAULT_MEM_LIMIT in tty_buffer.c. When the room runs out, a write from the terminal app's side goes through n_tty_write(), which does one of two things. If the file descriptor is non-blocking, it returns -EAGAIN. If it's blocking, it sleeps in wait_woken() until the reader frees space.

So the terminal app ends up waiting on a program that may never read again. If that write runs on a thread the rest of the app needs, or holds a lock the resize and close paths also take, the hang spreads from one tab to the whole window. Any terminal host has to queue paste data itself and feed it in as the PTY drains, never block on it.

I build Hermes IDE, so weigh this part with that in mind. The 2.2.9 release on October 12, 2026 fixed this class of bug in Hermes. The note reads: "Pasting a lot of text into a terminal whose program isn't reading it no longer freezes the whole window when you resize, split or close that terminal." The note doesn't describe the patch, so I won't guess at the internals. A big log pasted into a terminal whose program has stopped reading, then a resize, split or close of that pane, is the case the note describes.

Why agent TUIs switch to raw mode

Interactive CLIs, coding agents included, don't want the kernel editing their input. They want every key the moment it's pressed, so they can draw their own prompt, handle arrow keys, and collapse a pasted block, which is what raw mode gives them. In Node, setRawMode(true) means "input is always available character-by-character" and "all special processing of input characters by the terminal is disabled, including echoing input characters." Even Ctrl-C stops sending SIGINT.

Claude Code's interactive UI was built this way. A May 2025 issue shows the error it threw when it was run with piped input: "Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default." Ink is the React renderer for terminals, and its setRawMode throws when stdin can't do it.

Raw mode fixes the 1,024-byte wall, since the program reads bytes as they come and keeps the line in its own memory. It creates a new problem: a paste now looks like very fast typing. A newline inside the paste looks like you pressed Enter. That's what bracketed paste is for. The program sends ESC [ ? 2004 h, and from then on, per the xterm control sequences, the terminal wraps every paste in ESC [ 200 ~ and ESC [ 201 ~ "so that the program can differentiate pasted text from typed-in text." Ink's usePaste hook turns this on automatically, and GNU Readline has enable-bracketed-paste, whose default "is 'On'."

One sharp edge: the end marker can be smuggled inside the pasted text. The Ghostty paste docs flag data containing \x1b[201~ as unsafe because it "can be used to exit bracketed paste mode and inject commands." Copying a command from a web page into an agent's prompt is still pasting into a terminal.

What to do with this

When a long paste comes out short or never submits, ask how the program on the other end reads before blaming the app. A plain read() in canonical mode is the usual suspect.

  • For long input to your own scripts, use a pipe or a file: pbpaste | ./tool or ./tool < trace.txt. Pipes and files have no line discipline.
  • If you write a CLI that takes pasted input, read in non-canonical mode or use a line editor that does, and turn on bracketed paste.
  • If you write anything that types into a terminal for the user, the way that deep link did, keep each line under 1,024 bytes on macOS or pass the payload through a file or environment variable.
  • If you build a terminal host, treat writes to the PTY as something that can wait forever.

The 1,024 is one #define in a kernel header. It still decides whether your agent gets your whole stack trace.


If this was useful

The hang half of this post is a bounded buffer doing its job: when the consumer stops, the producer has to wait or drop data, and the only design choice is which. If you like thinking about where data waits and what happens when it can't, that's the ground covered by System Design Pocket Guide: Fundamentals, the core building blocks of systems that have to keep working under load. It's one of 8 short books in the Pocket Guides for Developers series, on agents, RAG, prompts, observability, events, databases and system design: the whole series is here. And if you run Claude Code and other agents side by side, Hermes IDE is free, source-available and on GitHub.

System Design Pocket Guide: Fundamentals — Core Building Blocks for Scalable Systems

Top comments (0)