ai newsDo., 15. Okt.
EilmeldungMeta and Apple team up to make Muse AI models run better on Macs

macOS begrenzt Eingaben an Line-Mode-Prompts auf 1.024 Zeichen

Wer mehr als 1.024 Zeichen gleichzeitig in ein Prompt im Line-Mode einfügt, stößt an eine Grenze des TTY-Systems von macOS. Der Rest wird kommentarlos abgeschnitten, was vor allem bei langen Prompts für Coding-Agenten zum Problem wird.

dev.toMittwoch 23:05 · 14.10.DE8 Min. Lesezeit
Bild: dev.to

Im Juli 2026 meldete jemand das Issue #81485 gegen Claude Code: Unter macOS schlug ein Deep Link, der eine Session durch Eingabe eines Startbefehls in ein Terminal öffnet, ohne jegliche Meldung fehl, sobald dieser Befehl länger als 1.024 Bytes war. Der Bericht beschreibt eine "Kürzung exakt an der 1024-Byte-Grenze, Befehl wird nie ausgeführt, keine Session." Die Terminal-Glocke läutete. Der Befehl blieb gekürzt am Shell-Prompt stehen, sein abschließender Zeilenumbruch war verschwunden.

iTerm2 tat das nicht, und auch Terminal.app nicht, wo der Bericht das Problem ebenfalls reproduzieren konnte. Der Kernel tat es. Jede Taste und jedes Einfügen, das man an ein Unix-Terminal schickt, läuft durch eine Schicht, an die die meisten Entwickler nie denken, und diese Schicht hat eine Größenbegrenzung für die Zeile, die man gerade tippt. Sie unterscheidet sich je nach Betriebssystem. Sie erklärt auch ein zweites Problem: ein großes Einfügen, das das Terminal einfrieren lässt.

Ein Terminalfenster kommuniziert über ein Pseudo-Terminal mit deinem Programm, ein Paar von Kernel-Geräten. Die Terminal-App schreibt deine Tastenanschläge in das eine Ende, dein Programm liest am anderen, und dazwischen sitzt die Line Discipline. Standardmäßig läuft sie im kanonischen Modus, den die Linux-Manpage termios(3) nüchtern beschreibt: "Eingaben werden zeilenweise verfügbar gemacht", und die Zeilenbearbeitung (erase, kill) ist aktiviert.

Deshalb funktioniert die Rückschritttaste in cat, obwohl cat keinen eigenen Bearbeitungscode hat. Der Kernel hält die Zeile in seinem eigenen Puffer, wendet dort deine Rückschritt- und Ctrl-U-Eingaben an und übergibt die Zeile erst an read(), wenn du Enter drückst. Bis dahin sieht das Programm nichts.

stty -a | grep -o -- '-\{0,1\}icanon' Enter fullscreen mode Exit fullscreen mode icanon bedeutet, dass der kanonische Modus aktiv ist. -icanon bedeutet, dass er ausgeschaltet ist. stty sane, der Reset, den die Manpage aufführt, schaltet ihn wieder ein.

Der Zeilenpuffer des Kernels hat eine feste Größe. Merke dir das.

Unter macOS liegt das Limit in XNUs 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 Der Code, der es durchsetzt, ist ttyinput() in XNUs bsd/kern/tty.c. Sobald die eingereihte Eingabe MAX_INPUT erreicht, wird jedes neue Zeichen verworfen, und bei gesetztem IMAXBEL lässt der Kernel die Glocke läuten. Signaltasten (Ctrl-C), Flusssteuerung sowie die erase- und kill-Tasten werden vor der Überlaufprüfung behandelt. Der Zeilenumbruch und die EOF-Taste werden danach behandelt. Bei einer vollen Zeile funktionieren Ctrl-C und die Rückschritttaste also weiterhin, aber Enter und Ctrl-D werden wie jedes andere Zeichen verworfen. Der Claude-Code-Befehl wurde nie ausgeführt, weil sein Enter nie ankam.

Ein Thread in den Apple Developer Forums aus dem Jahr 2024 zeigt dasselbe aus Nutzersicht: Man starte base64, füge ein, und "Ich kann bis zu 1024 Zeichen (Bytes) einfügen oder tippen. Danach piept das Terminal und erlaubt mir keine weitere Eingabe oder kein weiteres Einfügen mehr." Apples DTS-Ingenieur antwortete, dass das Limit "daher kommt, wie die Shell und das zugrunde liegende tty ihr I/O verwalten, nicht von terminal.app."

Linux erlaubt eine längere Zeile und behandelt den Überlauf anders. termios(3) setzt die maximale Zeilenlänge auf "4096 Zeichen (einschließlich des abschließenden Zeilenumbruchs)" und stellt fest: "Jegliche Eingabedaten nach 4095 Zeichen bis zu (aber nicht einschließlich) einem abschließenden Zeilenumbruch werden verworfen." Der Puffer dahinter ist N_TTY_BUF_SIZE, definiert als 4096 in drivers/tty/n_tty.c. Linux behält den Zeilenumbruch, sodass deine gekürzte Zeile trotzdem übermittelt wird. macOS verwirft ihn, sodass die Zeile einfach liegen bleibt.

Keines der beiden Betriebssysteme sagt deinem Programm Bescheid. Es erhält eine kürzere Zeile und hat keine Möglichkeit, das zu wissen.

Lege eine 5.000 Zeichen lange Zeile ohne Zeilenumbruch in die Zwischenablage. Unter macOS:

head -c 5000 /dev/zero | tr '\0' a | pbcopy Enter fullscreen mode Exit fullscreen mode Unter Linux leitest du dasselbe head | tr in das Zwischenablage-Werkzeug deiner Desktop-Umgebung. Führe nun wc -c aus, füge ein, drücke Enter und dann Ctrl-D:

wc -c Enter fullscreen mode Exit fullscreen mode Aus den obigen Quellen ergibt sich folgende Erwartung. Unter Linux, so termios(3), behältst du 4.095 Zeichen plus den Zeilenumbruch, sodass wc 4096 statt 5001 ausgeben sollte. Unter macOS läutet die Glocke nach 1.024 Bytes, und Enter und Ctrl-D bewirken nichts, weil sie ebenfalls verworfen werden. Drücke Ctrl-C, um herauszukommen.

Nun dasselbe Einfügen, wobei der kanonische Modus nur für diesen einen Befehl ausgeschaltet wird:

stty -icanon min 1; head -c 5000 | wc -c; stty icanon Enter fullscreen mode Exit fullscreen mode -icanon schaltet die Zeilenzusammensetzung ab, und min 1 (laut stty-Manpage: "mit -icanon mindestens N Zeichen für eine abgeschlossene Lesung setzen") sorgt dafür, dass jedes read() zurückkehrt, sobald ein Byte da ist. head entnimmt die Bytes, sobald sie ankommen, sodass keine Zeile jemals in den Kernel-Puffer passen muss. Wenn das TTY der Übeltäter war, meldet dieser Befehl 5000. Der Apple-Ingenieur in jenem Forumsthread beschreibt, dass das Einfügen einer einzelnen sehr langen Zeile in pico problemlos funktioniert. Ein Vollbild-Editor wie pico liest im nicht-kanonischen Modus, sodass der Zeilenpuffer sich nie füllt.

Die Kürzung erfordert ein Programm, das liest. Das andere Problem erfordert eines, das nicht liest: einen Prozess, der gerade kompiliert, schläft oder feststeckt, während man ihm einige tausend Zeilen Stack Trace hinein ​​fügt.

Die Bytes müssen trotzdem irgendwo hin. Unter Linux landen sie im 4.096-Byte-Lese-Puffer der Line Discipline, und sobald dieser voll ist, nimmt die Line Discipline keine Daten mehr an, und die Bytes stauen sich in den Flip-Buffern des pty. Der Linux-pty-Treiber hat ein pty_write_room(), das meldet, wie viel Platz auf der anderen Seite übrig ist, und die dahinterliegenden Flip-Buffer haben ein Byte-Limit, TTYB_DEFAULT_MEM_LIMIT in tty_buffer.c. Wenn der Platz aufgebraucht ist, läuft ein Schreibvorgang von der Seite der Terminal-App durch n_tty_write(), das eines von zwei Dingen tut. Bei einem nicht blockierenden Datei-Deskriptor gibt es -EAGAIN zurück. Bei einem blockierenden schläft es in wait_woken(), bis der Leser Platz freimacht.

Die Terminal-App wartet also am Ende auf ein Programm, das möglicherweise nie wieder liest. Wenn dieser Schreibvorgang auf einem Thread läuft, den der Rest der App braucht, oder eine Sperre hält, die auch die Resize- und Close-Pfade belegen, breitet sich das Einfrieren von einem Tab auf das gesamte Fenster aus. Jeder Terminal-Host muss die Daten zum Einfügen selbst in eine Warteschlange stellen und sie nachschieben, während das PTY abfließt, und darf dabei niemals blockieren.

Ich baue Hermes IDE, also bewerte diesen Teil vor diesem Hintergrund. Das Release 2.29 vom 12. Oktober 2026 behob diese Klasse von Fehlern in Hermes. Die Anmerkung lautet: "Das Einfügen viel Texts in ein Terminal, dessen Programm ihn nicht liest, friert das gesamte Fenster nicht mehr ein, wenn du die Größe änderst, es teilst oder es schließt." Die Anmerkung beschreibt den Patch nicht, daher rate ich nicht zu den Details. Ein großer Log-Auszug, der in ein Terminal eingefügt wird, dessen Programm aufgehört hat zu lesen, gefolgt von einem Resize, Split oder Close dieses Bereichs, ist der Fall, den die Anmerkung beschreibt.

Interaktive CLIs, Coding Agents eingeschlossen, wollen den Kernel nicht ihre Eingaben bearbeiten lassen. Sie wollen jede Taste im Moment des Drucks, damit sie ihren eigenen Prompt zeichnen, die Pfeiltasten verarbeiten und einen eingefügten Block zusammenfassen können, was der Raw-Modus ihnen ermöglicht. In Node bedeutet setRawMode(true), dass "Eingaben immer zeichenweise verfügbar" sind und "jegliche spezielle Verarbeitung von Eingabezeichen durch das Terminal deaktiviert ist, einschließlich des Echo der Eingabezeichen." Selbst Ctrl-C sendet kein SIGINT mehr.

Claude Codes interaktive UI wurde genau so gebaut. Ein Issue vom Mai 2025 zeigt den Fehler, den es beim Ausführen mit gepipter Eingabe warf: "Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default." Ink ist der React-Renderer für Terminals, und sein setRawMode wirft einen Fehler, wenn stdin das nicht unterstützt.

Der Raw-Modus beseitigt die 1.024-Byte-Mauer, da das Programm die Bytes liest, sobald sie kommen, und die Zeile in seinem eigenen Speicher behält. Er schafft ein neues Problem: Ein Einfügen sieht nun aus wie sehr schnelles Tippen. Ein Zeilenumbruch im eingefügten Text sieht aus wie ein gedrücktes Enter. Dafür gibt es bracketed paste. Das Programm sendet ESC [? 2004 h, und von da an umschließt das Terminal gemäß den xterm control sequences jede Einfügung mit ESC [ 200 ~ und ESC [ 201 ~, "damit das Programm eingefügten Text von getipptem Text unterscheiden kann." Inks usePaste-Hook schaltet dies automatisch ein, und GNU Readline hat enable-bracketed-paste, dessen Standardwert "On" ist.

Eine scharfe Kante: Das Endmarker kann in den eingefügten Text geschmuggelt werden. Die Ghostty-Paste-Dokumentation kennzeichnet Daten, die \x1b[201~ enthalten, als unsicher, weil sie "verwendet werden können, um den Bracketed-Paste-Modus zu verlassen und Befehle einzuschleusen." Das Kopieren eines Befehls von einer Webseite in den Prompt eines Agents bleibt weiterhin ein Einfügen in ein Terminal.

Wenn ein langes Einfügen abgeschnitten ankommt oder nie übermittelt wird, frage zunächst, wie das Programm am anderen Ende liest, bevor du der App die Schuld gibst. Ein einfaches read() im kanonischen Modus ist der übliche Verdächtige.

Die 1.024 ist ein einzelnes #define in einem Kernel-Header. Trotzdem entscheidet sie darüber, ob dein Agent deinen kompletten Stack Trace bekommt.

Der hängende Teil dieses Beitrags ist ein bounded buffer, der seine Arbeit macht: Wenn der Consumer stoppt, muss der Producer warten oder Daten verwerfen, und die einzige Designentscheidung besteht darin, welches davon gewählt wird. Wenn Sie gerne darüber nachdenken, wo Daten warten und was passiert, wenn dies nicht möglich ist, dann ist genau das das Thema des System Design Pocket Guide: Fundamentals, der zentralen Bausteine von Systemen, die auch unter Last weiterlaufen müssen. Es ist eines von 8 kurzen Büchern der Reihe Pocket Guides for Developers, die sich mit agents, RAG, Prompts, Observability, Events, Datenbanken und Systemdesign beschäftigt: die gesamte Reihe finden Sie hier.

Und wenn Sie Claude Code und andere Agents parallel betreiben, ist Hermes IDE kostenlos, als Quellcode verfügbar und auf GitHub zu finden.

Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.

Original (Englisch) anzeigen

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.

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.

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.

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.

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.

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.

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.

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.

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

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.

Originaltitel: Paste 2,000 Characters at a Line-Mode Prompt and macOS Keeps 1,024. Blame the TTYQuelle: dev.to (dev.to). Original öffnen