Skip to content

fix: Ctrl+C cascade state machine (clear → abort → exit) - #941

Merged
saumas merged 6 commits into
getkimchi:masterfrom
saumas:ctrl-c-cascade-clear-abort-exit
Aug 4, 2026
Merged

fix: Ctrl+C cascade state machine (clear → abort → exit)#941
saumas merged 6 commits into
getkimchi:masterfrom
saumas:ctrl-c-cascade-clear-abort-exit

Conversation

@saumas

@saumas saumas commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Problem

When the agent is working and the user navigates through previous messages with up/down arrows, pressing Ctrl+C to clean the input box also aborts the agent — requiring a "please continue" prompt afterward.

Root cause

In src/extensions/ui.ts, the global onTerminalInput listener intercepted Ctrl+C (\x03) and called currentCtx.abort() when the agent was streaming, but returned plain undefined instead of { consume: true }. Since undefined doesn't stop propagation in pi-tui's handleInput, the event flowed through to upstream's app.clear handler (handleCtrlC()) which cleared the editor text. So Ctrl+C simultaneously aborted the agent AND cleared text.

Solution

Replaced the single-shot abort with a cascading state machine:

State Action Event handling
Text exists Clear text Let event flow to upstream app.clear
No text, streaming Abort agent Consume event ({ consume: true })
No text, idle Exit timer Check own 500ms timer, shut down if within window

The exit timing is managed entirely in the extension layer via lastCtrlCTime (updated on every press), not upstream's lastSigintTime, to avoid a timing gap where the abort press consumes the event without updating upstream's timer.

A status hint "Ctrl+C again to abort" is shown after the first press clears text while the agent is streaming, and is cleared on turn_end.

The decision logic is extracted into a pure function ctrlCCascadeDecision(hasText, isStreaming) for testability. ESC behavior is unchanged (immediate abort, no text clearing).

Test plan

  • Added unit tests for ctrlCCascadeDecision covering all state transitions
  • Manual test: type text while agent streaming → Ctrl+C clears text → Ctrl+C again aborts → Ctrl+C again exits
  • Manual test: idle + text → Ctrl+C clears → Ctrl+C exits (preserved behavior)
  • Manual test: streaming + no text → Ctrl+C aborts → Ctrl+C exits

Closes #944

@readme-ai-writer

readme-ai-writer Bot commented Jul 30, 2026

Copy link
Copy Markdown

📚 No documentation changes were needed for this PR.

@kimchi-review

kimchi-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

Kimchi Code Review

Property Value
Commit c60c6a1
Author @saumas
Files changed 0
Review status Completed
Comments 1 (1 warning)
Duration 1090s

Summary

📊 Review Score: 85/100 (overall code quality — 0 lowest, 100 highest)
⏱️ Estimated effort to review: 2/5 (1 = trivial, 5 = very complex)

🧪 Tests: yes — Unit tests added for the pure ctrlCCascadeDecision function cover all three outcomes and the priority ordering. The integration path inside onTerminalInput is not directly tested, which is reasonable given the current test style, but the decision logic itself is well covered.

📝 Found 1 issue(s). See inline comments for details.

What to expect

Kimchi will analyze the changes in this pull request and post:

  • A summary of the overall changes
  • Inline comments on specific lines with findings categorized by issue type

The review typically completes within a few minutes. This comment will be updated once the review is ready.

Interact with Kimchi
  • @getkimchi review — re-trigger a full review on the latest commit
  • @getkimchi summary — regenerate the PR summary
  • @getkimchi ignore — skip this PR (no review will be posted)
  • Reply to any inline comment to ask follow-up questions or request clarification
Configuration

Reviews are configured by your organization admin.
Review instructions, excluded directories, and severity thresholds can be adjusted per repository in the Kimchi dashboard.


Powered by Kimchi — AI-powered code review by CAST AI

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👋 Thanks for your first PR on kimchi! A maintainer will review it soon.
Please make sure your PR links to an open issue and your checklist is complete.

@kimchi-review kimchi-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📊 Review Score: 85/100 (overall code quality — 0 lowest, 100 highest)
⏱️ Estimated effort to review: 2/5 (1 = trivial, 5 = very complex)

🧪 Tests: yes — Unit tests added for the pure ctrlCCascadeDecision function cover all three outcomes and the priority ordering. The integration path inside onTerminalInput is not directly tested, which is reasonable given the current test style, but the decision logic itself is well covered.

📝 Found 1 issue(s). See inline comments for details.

Comment thread src/extensions/ui.ts
@saumas
saumas force-pushed the ctrl-c-cascade-clear-abort-exit branch 2 times, most recently from 5ca1fd9 to e49ffb0 Compare July 31, 2026 15:26
Saulius Mašnauskas added 6 commits August 4, 2026 18:03
Previously, Ctrl+C while the agent was streaming would simultaneously abort
the agent AND clear the editor text (because the terminal input listener
called abort() but returned undefined, letting the event flow through to
upstream's app.clear handler). This meant the user lost their in-progress
text without any way to just clear it first.

Now Ctrl+C follows a cascading state machine:
  1. Text exists → clear text (let upstream handle it)
  2. No text, streaming → abort agent (consume event)
  3. No text, idle → let upstream handle double-press-to-exit

A status hint 'Ctrl+C again to abort' is shown after the first press clears
text while the agent is streaming, and is cleared on turn_end.

The decision logic is extracted into a pure function ctrlCCascadeDecision()
for testability. ESC behavior is unchanged (immediate abort, no text clearing).

Test: added ctrlCCascadeDecision unit tests covering all state transitions.
…Time

The review identified a severe issue: the abort stage consumes the event,
preventing upstream's handleCtrlC from updating lastSigintTime. This meant
the exit stage's 500ms window was measured from the clear press (stage 1),
not the abort press (stage 2), causing:

1. Unintended exit on rapid triple-press (clear → abort → exit within 500ms
   measured from clear, not abort)
2. Unreliable '3rd press exits' for slower users (>500ms from clear to exit)

Fix: the exit stage now checks its own lastCtrlCTime (updated on every press,
including abort) and calls shutdown() directly. The event is consumed in the
exit stage so upstream's handleCtrlC never fires, preventing a competing
lastSigintTime that could cause unintended exits later.
The codebase uses brief comments explaining why (1-5 lines), not
what. Trimmed the listener block comment from 14 to 3 lines, the
JSDoc from 10 to 1 line, and each switch case comment to 1 line.
@saumas
saumas force-pushed the ctrl-c-cascade-clear-abort-exit branch from 2582970 to 433a61c Compare August 4, 2026 15:03
@saumas
saumas added this pull request to the merge queue Aug 4, 2026
Merged via the queue into getkimchi:master with commit 13f4242 Aug 4, 2026
5 checks passed
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.

Ctrl+C simultaneously aborts agent and clears input text instead of clearing first

2 participants