From e045283a4e86a313dc0b3e9b284455b262c5cb1c Mon Sep 17 00:00:00 2001 From: Gyanu Date: Wed, 26 Aug 2026 09:11:34 +0530 Subject: [PATCH] Stop using Bash bind -x for llm chat arrow keys. pyreadline3 on Windows does not implement that syntax, so chat could fail while setting up left/right cursor movement. GNU readline bind syntax works on both platforms; ignore bind errors from readline stand-ins. --- llm/cli.py | 10 +++++----- tests/test_chat.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/llm/cli.py b/llm/cli.py index 3123a387f..45ed17872 100644 --- a/llm/cli.py +++ b/llm/cli.py @@ -1267,13 +1267,13 @@ def chat( """ Hold an ongoing chat with a model. """ - # Left and right arrow keys to move cursor: - if sys.platform != "win32": + # Left and right arrow keys to move cursor. + # pyreadline3 on Windows does not implement Bash `bind -x`. + try: readline.parse_and_bind("\\e[D: backward-char") readline.parse_and_bind("\\e[C: forward-char") - else: - readline.parse_and_bind("bind -x '\\e[D: backward-char'") - readline.parse_and_bind("bind -x '\\e[C: forward-char'") + except Exception: + pass log_path = pathlib.Path(database) if database else logs_db_path() (log_path.parent).mkdir(parents=True, exist_ok=True) db = sqlite_utils.Database(log_path) diff --git a/tests/test_chat.py b/tests/test_chat.py index 120f757f7..d8410f374 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -355,3 +355,35 @@ def test_chat_fragments(tmpdir): ).output assert '"prompt": "one' in output assert '"prompt": "two"' in output + + +def test_chat_readline_does_not_use_bash_bind_x(mock_model, logs_db, monkeypatch): + calls = [] + monkeypatch.setattr( + llm.cli.readline, "parse_and_bind", lambda spec: calls.append(spec) + ) + mock_model.enqueue(["ok"]) + result = CliRunner().invoke( + llm.cli.cli, + ["chat", "-m", "mock"], + input="Hi\nquit\n", + catch_exceptions=False, + ) + assert result.exit_code == 0 + assert calls == ["\\e[D: backward-char", "\\e[C: forward-char"] + + +def test_chat_survives_readline_bind_errors(mock_model, logs_db, monkeypatch): + def boom(_spec): + raise ValueError("pyreadline3 does not support this bind") + + monkeypatch.setattr(llm.cli.readline, "parse_and_bind", boom) + mock_model.enqueue(["ok"]) + result = CliRunner().invoke( + llm.cli.cli, + ["chat", "-m", "mock"], + input="Hi\nquit\n", + catch_exceptions=False, + ) + assert result.exit_code == 0 + assert "ok" in result.output