Skip to content

chat prompt emits raw readline markers (\x01/\x02) because readline is never imported #1653

Description

@Vlor999

Summary

corridor_input() in mlx_lm/cli_ui.py wraps the prompt's ANSI escapes in
\x01 / \x02:

prompt = _ANSI_RE.sub(lambda m: f"\x01{m.group(0)}\x02", cap2.get())
return input(prompt)

Those two bytes are readline's markers for "this run of bytes is
non-printing, do not count it toward the line width". They only mean anything if
the readline module has been imported, because that is what makes input()
route through readline. Nothing in mlx_lm imports it, and Python does not
import it automatically for non-interactive scripts.

So today the markers are written to the terminal verbatim.

Reproducer

import os, pty, sys

SCRIPT = 'import sys\n{imp}\ntry: input("\\x01\\033[1m\\x02> ")\nexcept EOFError: pass\n'

def run(imp):
    pid, fd = pty.fork()
    if pid == 0:
        os.execv(sys.executable, [sys.executable, "-c", SCRIPT.format(imp=imp)])
    os.write(fd, b"hi\n")
    out = b""
    try:
        while True:
            chunk = os.read(fd, 1024)
            if not chunk:
                break
            out += chunk
    except OSError:
        pass
    os.waitpid(pid, 0)
    return out

for label, imp in (("without readline", ""), ("with readline", "import readline")):
    out = run(imp)
    print(f"{label:18} | raw 0x01: {b chr(1) in out if False else (chr(1).encode() in out)}")

Under a real PTY:

raw 0x01 emitted raw 0x02 emitted
without readline yes yes
with readline no no

Two consequences

  1. The escape markers leak. Cosmetic, but it is stray control output the
    code plainly did not intend — the markers exist precisely to be consumed.
  2. No history or line editing. mlx_lm.chat has no Up/Down history, no
    Left/Right cursor movement, and nothing persists between sessions. Importing
    readline fixes 1 and delivers 2 at the same time.

Suggested fix

Import readline (guarded — it is unavailable on some platforms) and
load/save a history file around the chat session. Both should be rank 0 only,
matching the existing ChatUI rank gating.

Happy to open a PR; I have it ready with tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions