Skip to content

Bug: Mouse escape sequences leak into stdin after prolonged use, causing garbled input #244

@lijunzh

Description

@lijunzh

Bug Description

After extended use of Code Puppy, mouse actions in the terminal (or other apps) start sending weird escape sequence characters into the Code Puppy prompt, resulting in meaningless garbled input.

Root Cause Analysis

Three interacting bugs compound over long sessions:

Bug 1 (Primary): _listen_for_ctrl_x_posix corrupts stdin by not draining multi-byte escape sequences

Files: code_puppy/agents/base_agent.py (line ~1737), code_puppy/tools/command_runner.py (line ~363)

During every agent execution, a background thread sets the terminal to cbreak mode and reads stdin one byte at a time. It only checks for Ctrl+X or the cancel key and silently discards all other bytes.

The problem: mouse events and terminal escape sequences are multi-byte (e.g., a mouse click generates \x1b[<0;15;20M — 7+ bytes). When the listener stops mid-sequence (stop_event.set() fires between bytes), the remaining bytes stay in the stdin buffer. When prompt_toolkit takes over for the next prompt, those orphaned bytes appear as garbled characters.

Compare to ask_user_question/tui_loop.py line 66-69 which correctly drains trailing escape bytes:

if ch == "\x1b":
    while select.select([sys.__stdin__], [], [], 0.01)[0]:
        sys.__stdin__.read(1)  # drain the rest of the escape sequence

The cbreak listeners do NOT do this.

Bug 2 (Secondary): MCP Custom Server Form leaks mouse tracking escape sequences

File: code_puppy/command_line/mcp/custom_server_form.py (line ~614)

This is the only component in the entire codebase with mouse_support=True. All other TUI components correctly use mouse_support=False.

When this form runs, prompt_toolkit enables mouse tracking (\x1b[?1000h, \x1b[?1003h, \x1b[?1006h). It uses app.run(in_thread=True), and if cleanup fails (thread race condition, exception during alternate screen buffer exit), mouse tracking stays permanently enabled for the rest of the session. Every subsequent mouse action generates escape sequences that feed into stdin as garbage.

Bug 3 (Contributing): Concurrent stdin access between cbreak listener and prompt_toolkit

The cbreak listener thread reads stdin simultaneously with any prompt_toolkit Application the agent spawns (e.g., ask_user_question). Both fight over the same file descriptor, creating race conditions where one thread steals bytes that another needs, fragmenting escape sequences.

Steps to Reproduce

  1. Start Code Puppy and use it for an extended session (many agent interactions)
  2. Optionally use /mcp to add a custom server (triggers the mouse_support=True form)
  3. After many interactions, click or scroll with mouse in the terminal
  4. Observe garbled escape sequence characters appearing in the prompt

Expected Behavior

Mouse actions should never produce visible characters in the Code Puppy prompt. Terminal state should be fully restored after every TUI interaction.

Proposed Fix

  1. Add escape sequence draining to both _listen_for_ctrl_x_posix functions (in base_agent.py and command_runner.py) — when \x1b is read, drain all subsequent bytes within a short timeout
  2. Change custom_server_form.py to use mouse_support=False (consistent with every other TUI component), and add explicit mouse tracking disable in the finally block as a safety net
  3. Add a terminal state reset helper to terminal_utils.py that explicitly disables mouse tracking and bracketed paste, callable from cleanup paths

Environment

  • macOS (posix)
  • Terminal: iTerm2 / Terminal.app
  • Code Puppy latest

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions