Your Half-Typed Prompt Lives Inside the Agent, Not the Terminal. Injected Commands Hit It
Book: Prompt Engineering Pocket Guide The series: Pocket Guides for Developers — 8 short books on agents, RAG, prompts, observability, events, databases and system design — the whole series My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools M

Posted on Oct 31 • Edited on Oct 31 • AI-assisted
You are halfway through a sentence in Claude Code: fix the flaky login test but don't touch. In another window, a helper script decides the context is getting full and runs tmux send-keys -t agent '/compact' Enter.
Nothing in the terminal stops the agent's input box from ending up as fix the flaky login test but don't touch/compact, and the Enter that came with it will submit whatever the box holds. You never finished the sentence, and the command never runs as a command.
None of this is a bug in Claude Code or in tmux. It follows from where a half-typed prompt lives once a full-screen agent owns the terminal. Any tool that sends /compact or /model into a running agent has to deal with it.
A normal shell prompt runs in canonical mode. The termios man page says that in this mode "input is made available line by line" and "line editing is enabled". The kernel's line discipline holds the line you are typing, handles Backspace, and only hands the program the finished line when you press Enter.
Full-screen agents switch that off. Claude Code is built on Ink, which lists it under "Who's Using Ink?" and exposes setRawMode for reading keys. Node's setRawMode makes input "available character-by-character" with "all special processing of input characters by the terminal" disabled. Codex's TUI depends on ratatui and crossterm, and crossterm's raw mode means input is "not line buffered (input sent byte-by-byte to input buffer)".
In termios terms, raw mode clears ICANON and ECHO (see cfmakeraw in the same man page ). With ICANON off, "input is available immediately" and "line editing is disabled". The kernel stops holding a line. Every keystroke goes straight to the agent, and the agent keeps the draft in its own memory, draws it on screen, and decides what Backspace or Enter means.
You can see the switch. In a plain shell:
stty -a | grep icanon Enter fullscreen mode Exit fullscreen mode The output contains icanon with no minus sign. Now run tty in a terminal, note the device, start claude there, and from a second terminal point stty at that device ( -F on Linux, -f on macOS and BSD ):
# Linux stty -a -F /dev/pts/3 | grep icanon # macOS stty -a -f /dev/ttys003 | grep icanon Enter fullscreen mode Exit fullscreen mode While the agent's interface is up, expect -icanon and -echo. The line buffer you might have hoped to inspect does not exist. Your draft is a string inside a Node or Rust process, and short of attaching a debugger, nothing outside the process can read it. The closest anything outside can get is the rendered screen.
In raw mode the kernel passes every byte through, and the draft lives only inside the agent process.
You don't need an agent to see it. The first file is a toy agent that does what a raw-mode TUI does: it switches its terminal to raw mode and keeps its own draft.
# toy_agent.py: keeps its own draft, like a TUI import os, sys, tty tty. setraw ( sys. stdin. fileno ()) draft = b "" while True: ch = os. read ( 0, 1 ) if ch == b " \r ": os. write ( 1, b " SUBMITTED: " + draft + b " \r\n " ) draft = b "" elif ch == b " \x7f ": draft = draft [: - 1 ] elif ch == b " \x04 ": break else: draft += ch Enter fullscreen mode Exit fullscreen mode The second file plays both you and the tool. It starts the toy on a pseudo-terminal with pty.fork(), whose parent gets "a file descriptor connected to the child's controlling terminal". Writing to that descriptor is exactly what a terminal emulator does when you press a key, and what tmux send-keys does for a script.
# inject.py: you type half a prompt, a tool sends /help import os, pty, sys, time pid, fd = pty. fork () if pid == 0: os. execvp ( sys. executable, [ sys. executable, " toy_agent.py " ]) time. sleep ( 0.5 ) # let the toy enter raw mode os. write ( fd, b " fix the fl " ) # you, mid-word time. sleep ( 0.2 ) os. write ( fd, b " /help \r " ) # the tool's command time. sleep ( 0.2 ) os. write ( fd, b " \x04 " ) # tell the toy to quit time. sleep ( 0.2 ) print ( os. read ( fd, 1024 )) os. waitpid ( pid, 0 ) Enter fullscreen mode Exit fullscreen mode Run python3 inject.py. The toy should submit one line, b'SUBMITTED: fix the fl/help\r\n': your fragment with /help glued to the end. It never saw a command.
Now change b"fix the fl" to b"" and run it again: the toy submits /help alone. That is the only case where injection behaves, and from outside the process you cannot tell which case you are in. (If the output starts with an echo of your text, the toy wasn't in raw mode yet; raise the first sleep.)
A real agent is more careful than the toy, with autocomplete, multi-line input and paste handling, but the bytes arrive the same way. I haven't run this against Claude Code or Codex myself, so read the toy as the mechanism. Each product's input handling is its own.
The first is the one above: the bytes are appended to a draft. Clearing the box first does not save you. In Claude Code, per the interactive mode docs, Ctrl+C with nothing running "clears the prompt input and a second press exits Claude Code". A script that sends Ctrl+C or Ctrl+U to get a clean box throws away the human's text to do it. Recent Claude Code versions can bring a cleared draft back with Up, but your script should not be relying on that.
The second is timing. The agent may still be working when the bytes land, and agents treat input during a turn on purpose:
Each of those behaviours makes sense for a human at the keyboard who knows the agent is busy. A script that doesn't know will hit them by accident.
Two writers, one input box: the agent cannot tell which bytes came from whom.
There are two, and both are about what the tool knows before it writes.
Send only when the agent says it is waiting. Don't infer that from a quiet screen. Ask the agent. Claude Code has hooks for this: Stop fires "when Claude finishes responding", and the Notification event can be matched on idle_prompt and permission_prompt. A tool that listens to those knows when a turn is over, which a screen scraper can only guess.
Never write on top of a draft. Since nothing outside the agent can read the draft, the safe version of this rule is structural: the command must not travel through the box the human is typing into. If a human might be mid-sentence in that box, the tool waits, or it hands the decision back to the human. Wiping the box to make room is not an option: it throws the human's text away.
For an unattended session where nobody types, tmux send-keys after a Stop hook is fine. The trouble starts when a person and a program share one input box.
I build Hermes IDE, so weigh this section with that in mind. Hermes runs each agent's own interface in a real terminal, so Claude Code's input box is Claude Code's, raw mode and all. The README says Hermes "never types into a terminal on its own".
Version 2.3.4, released on October 15, 2026, rebuilt the bar above each session: Model, Plan and Review in the same place for every agent, a context meter that offers Compact when the conversation gets close to full, and a Commands list with built-in commands plus your own commands and skills. Commands run when you click them, never on their own.
The action bar above a Claude Code session (Prompts, Context 4%, Model, Plan, Review, / Commands) with the Commands list open: pinned Context, Model, Plan and Review, then built-in commands such as Compact, Usage and Diff.
The fix in the same release follows the two rules above, in the release notes' own words: "Clicking a command never touches what you were typing, and it only runs when the agent is waiting." Waiting comes from Hermes's per-session status (working, needs approval, asked you, done, error or idle), and the README notes that Hermes says when a status is a guess. Hermes is free, source-available under BSL 1.1, and runs on macOS, Windows and Linux. It is not affiliated with Nous Research, Anthropic, OpenAI or any agent vendor.
If you write your own helper instead, the toy experiment above is a cheap regression test: put a fragment in the draft, fire your command, and check the fragment survives.
A slash command is a prompt that takes a shortcut, and where it lands decides what the model reads. The Prompt Engineering Pocket Guide covers the other half of that: what to put in the prompt once it arrives intact, from system prompts to context you feed the agent on purpose. It is one of 8 short books in the Pocket Guides for Developers series, on agents, RAG, prompts, observability, events, databases and system design. If you want to try the command bar, Hermes IDE is a free download and the code is on GitHub.