Skip to content

fix(keyboard): clear ISIG/IEXTEN in raw mode to prevent SIGTSTP suspension#1545

Open
n-WN wants to merge 1 commit intoMoonshotAI:mainfrom
n-WN:zoee/fix-keyboard-sigtstp
Open

fix(keyboard): clear ISIG/IEXTEN in raw mode to prevent SIGTSTP suspension#1545
n-WN wants to merge 1 commit intoMoonshotAI:mainfrom
n-WN:zoee/fix-keyboard-sigtstp

Conversation

@n-WN
Copy link
Copy Markdown
Collaborator

@n-WN n-WN commented Mar 22, 2026

Related Issue

Resolves #38can not handle CTRL+Z singal (reported by @yihong0618, closed as wontfix but never actually fixed).

Also related: #1480 (flow:skill not interruptible in interactive mode — the _LiveView path where this bug lives).

Description

The raw keyboard listener (keyboard.py) used in the standalone _LiveView visualization path only cleared ICANON and ECHO when entering raw mode, but left ISIG and IEXTEN set. With ISIG active, the kernel intercepts control characters before they reach the application:

  • Ctrl+ZSIGTSTP → process suspended (no handler installed → hung process)
  • Ctrl+CSIGINT → process killed

This does not affect the normal interactive shell, which uses prompt_toolkit (it correctly clears ISIG via tty.setraw). The bug manifests in the _LiveView path — activated when prompt_session is None (e.g., piped/non-interactive runs, or when an IME interferes with tmux prefix detection causing raw keys to reach the app).

Fix

keyboard.py (2 lines changed):

  • Clear ISIG and IEXTEN alongside ICANON and ECHO when configuring raw mode, so all control characters are delivered as bytes instead of signals.
  • Map \x03 (Ctrl+C) to a new KeyEvent.CTRL_C enum value.

visualize.py (1 line changed):

  • Handle CTRL_C the same as ESCAPE in dispatch_keyboard_event — set the cancel event to abort the active run.

Before / After (verified in tmux)

Metric origin/main This branch
ISIG flag SET CLEARED
IEXTEN flag SET CLEARED
Ctrl+Z SIGTSTP → process suspended Byte \x1a → silently ignored
Ctrl+C SIGINT → process killed Byte \x03CTRL_C event → cancels run
Arrow keys

Testing

  • 18 new unit tests in tests/ui_and_conv/test_keyboard_raw_mode.py covering:
    • Raw mode flag verification (ISIG, IEXTEN, ICANON, ECHO all cleared)
    • All 14 byte → KeyEvent mappings (parametrized)
    • CTRL_C dispatches cancel event in _LiveView
    • ESCAPE still dispatches cancel (regression test)
    • CTRL_C is a no-op when no cancel event is set
  • Full test suite: 1172 passed, 6 skipped, 0 failed
  • Ruff lint + format: clean

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked the related issue, if any.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have run make gen-changelog to update the changelog.

…nsion

The keyboard listener's Unix raw mode only cleared ICANON and ECHO,
leaving ISIG and IEXTEN set. This caused Ctrl+Z to generate SIGTSTP
and suspend the process (with no handler to recover), particularly
problematic when using IME + tmux where accidental Ctrl+Z is easy.

Changes:
- Clear ISIG and IEXTEN in raw-mode lflags, consistent with
  prompt_toolkit's tty.setraw() behavior
- Add CTRL_C key event so Ctrl+C (now a byte, not a signal) can
  cancel the run in the _LiveView fallback path
- Handle CTRL_C alongside ESCAPE in _LiveView.dispatch_keyboard_event
- Add 18 tests covering raw-mode flags, byte→event mapping, and
  cancel-event dispatch
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Windows keyboard listener missing CTRL_C byte handling added to Unix listener

The PR adds \x03KeyEvent.CTRL_C mapping to _listen_for_keyboard_unix at src/kimi_cli/ui/shell/keyboard.py:190-191, but the corresponding _listen_for_keyboard_windows function (lines 256-275) was not updated with the same case. This means on Windows, when using the standalone _LiveView visualization path, pressing Ctrl+C will never emit KeyEvent.CTRL_C, so the cancel-via-Ctrl+C feature (src/kimi_cli/ui/shell/visualize.py:1188) cannot trigger. Users on Windows would have to rely on ESC to cancel. The existing pattern shows both listeners should handle the same set of bytes (both already handle \x05 for CTRL_E).

(Refers to lines 262-263)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1351de3ed

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1187 to 1189
# handle ESC / Ctrl+C to cancel the run
if event in (KeyEvent.ESCAPE, KeyEvent.CTRL_C) and self._cancel_event is not None:
self._cancel_event.set()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route Ctrl+C through question panels as a cancel

Because dispatch_keyboard_event() returns from the question-panel branch before reaching this new CTRL_C cancellation path, pressing Ctrl+C during a QuestionRequest in _LiveView is now a no-op. That path is used whenever visualize() runs without a prompt session (src/kimi_cli/ui/shell/visualize.py:103-119), e.g. command-mode runs from Shell.run() (src/kimi_cli/ui/shell/__init__.py:163-167). In those runs, question-based tools treat an empty response as “dismissed” rather than cancelled (src/kimi_cli/tools/ask_user/__init__.py:120-126, src/kimi_cli/tools/plan/__init__.py:198-205), so users cannot abort the active run with Ctrl+C once a question panel is visible.

Useful? React with 👍 / 👎.

@n-WN
Copy link
Copy Markdown
Collaborator Author

n-WN commented Mar 22, 2026

To solve some key interaction problems, this PR is not ready yet, and may be related to other issues, which are being planned.

plz @RealKai42 Do not merge for now.

@yihong0618
Copy link
Copy Markdown
Contributor

FYI, we should also consider the behavior on windows

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

can not handle CTRL+Z singal

2 participants