The interactive shell for this tool runs on top of Node's built-in readline module. The attorney sees an aaicase> prompt, types a query in plain English, and presses Enter. Readline handles cursor movement, backspace, history navigation with the up and down arrows, and the standard emacs-style keybindings like Ctrl-A for start of line and Ctrl-E for end. Everything you'd expect from a Unix-style REPL.

On top of readline's defaults, the application adds one custom keybinding: Escape clears the current input line. If you've typed something, hit Esc, and the line empties. If a query is running, hit Esc, and the query cancels. Same key, context-aware behavior. The intent is to give the attorney a fast "I changed my mind, let me start over" gesture that doesn't require a separate Ctrl-U or backspace-many-times.

The Esc handler is registered as a raw byte listener on stdin. It checks for a buffer that starts with 0x1B (the ASCII code for Escape) and, depending on context, either cancels the running query or clears the readline buffer. It works correctly when the attorney types slowly: hit Esc, see the line clear, type the next command, see it appear character by character.

What hadn't been tested — until a deliberate probe this week — was what happens when the attorney is a fast typist.

Driving the REPL through expect

The probe was an expect script: spawn the aaicase process, wait for the prompt, send keystrokes, capture what the terminal received. Several scenarios got tested: typing then backspace (works), Ctrl-A then insert (works), arrow-up to recall history (works), paste of multi-line text (works). The one that failed was almost embarrassingly simple: type "garbage", press Esc, type "exit".

The output should have been "exit" appearing at the prompt after the line cleared. What appeared instead was "xit". The first character of "exit" — the 'e' — was being eaten.

The Esc handler was firing correctly. The terminal repaint showed the line going to empty after Esc. But the very next character typed was lost, every time, when the typing was rapid enough.

What readline does with Esc

The diagnostic that broke it open was a minimal repro: a plain Node script with just readline.createInterface, no custom handlers, just receiving raw byte sequences. The same pattern produced the same bug. The application's Esc handler wasn't the cause. Node's readline itself was.

Readline supports "meta key" sequences. In emacs-style keybindings, Meta-e (often produced by Alt-e) means "move forward one word." Meta-b means "move backward one word." The terminal encodes Meta-X as the two-byte sequence 0x1B X — Escape followed by the actual key. Vim users will recognize this; it's the same encoding scheme for everything from arrow keys to function keys.

Readline distinguishes "the user pressed Escape" from "the user pressed Meta-something" by timing. There's a setting called escapeCodeTimeout, default 500 milliseconds. When readline receives a bare 0x1B byte, it waits up to that timeout for a follow-up byte. If a byte arrives, it's treated as the second half of a meta-sequence. If the timeout expires, the bare Escape is treated as standalone.

The default is reasonable for the typical use case. Most legitimate multi-byte sequences (arrow keys, function keys, paste delimiters) arrive in a single buffer because the kernel coalesces reads from the terminal. The 500ms timeout is just insurance against pathological splitting. A human typing Escape and then a letter would usually take much longer than 500ms.

The exception is exactly the case we hit. A fast typist pressing Esc to clear a line and then immediately typing their next command can absolutely produce an Esc followed by a letter within 500ms. Readline interprets the combination as a meta-sequence. The application's Esc handler ALSO sees the Esc and clears the line. The letter is consumed by readline's meta interpretation and never reaches the application's input buffer.

The result, from the attorney's perspective: I pressed Esc, the line cleared, I started typing "exit", and somehow only "xit" appeared. The 'e' just... vanished. Repeat the experiment slowly and it works fine. Repeat it at normal typing speed and it fails about half the time. Repeat it as a fast burst (which is exactly what an attorney does when they realize they typed the wrong thing) and it fails reliably.

The fix

The fix is one line: pass escapeCodeTimeout: 50 to createInterface instead of accepting the 500ms default. 50 milliseconds is still long enough for the multi-byte sequences that arrive in a single buffer (those don't need any timeout — the buffer is whole when readline sees it). It's short enough to be invisible to human reaction time. The probability of a human pressing Esc and then a letter within 50ms is essentially zero.

Verified after the fix: type-Esc-type sequence now produces the full second command, every time, at every typing speed. Arrow-key navigation still works (those arrive coalesced). History recall still works. Paste of long text works (also coalesced). Every other readline behavior is unchanged.

What this says about testing

The system's safety-critical surfaces — the audit log, the override token verification, the binding guards — have had careful, repeated verification. We run end-to-end tests on every release. We probe with deliberately malformed inputs. We've found and fixed several real bugs in those layers over the past few weeks.

The interactive REPL had been getting verified by inspection: read the code, see that the Esc handler looks right, ship. It works correctly when tested slowly. Nobody had deliberately stress-tested it with the timing patterns of an actual typist until this week.

The lesson is small: testing keyboard input requires actually exercising it through a PTY, not just reading the code. Expect is one tool for this; node-pty is another; a manual session with someone actually trying to type fast is a third. The bug had been there since the Esc handler was added. It was visible to anyone who typed fast. It would have been caught the first time we tested with realistic timing.

The version on the install URL is 4.5.76. Small release. The work continues at the pace of one verified release at a time, and the keyboard input surface — the part the attorney actually touches — is now on the same verification footing as the rest of the system.