Skip to content

feat(ui): Ctrl-C cancels a model turn mid-stream#12

Merged
lavindeep merged 1 commit into
ui-reworkfrom
feat/ui-cancellation
Jun 28, 2026
Merged

feat(ui): Ctrl-C cancels a model turn mid-stream#12
lavindeep merged 1 commit into
ui-reworkfrom
feat/ui-cancellation

Conversation

@lavindeep

Copy link
Copy Markdown
Owner

Branch 6/9 of the UI v2 rework (model-stream cancellation). A long turn was a blind wait with no escape; now Ctrl-C aborts it cleanly.

What

  • Ctrl-C is a key press in the alt-screen (raw mode disables ISIG → not a SIGINT). It sets a per-turn cancel Event.
  • OllamaClient._stream_chat checks the event at each read boundary and raises a typed GenerationCancelled, closing the response in-thread via the with self._client.stream(...) context manager (the only cross-thread op is Event.set()). The exception is neither OllamaError nor an httpx error, so the think-retry and transport handler don't swallow it.
  • History integrity: the model call is in a try/finally that only closes the UI response; _record(reply) is after it, so a cancelled turn skips the record site — the user message stands, the partial reply is never stored.
  • The worker distinguishes cancel from failure: GenerationCancelled → clean abort_turn (clear indicator, mark ⏹ aborted); other exceptions → pane error; _mark_done clears busy on every path (no wedge). The cancel event is passed as a thread argument so the worker never reads the mutable self._cancel (invariant holds literally).

Scope

Subprocess killpg is branch 6b — a cancel during a running command aborts the turn once the command returns. No approval/queue (branches 7/9).

Verification

  • Gate green: ruff + mypy --strict, 1368 passed.
  • Two independent reviews, both SHIP (sequenced to avoid collision): Opus skeptical proved history integrity + 5 hollow-test guards red-first; Codex grounded the in-thread-close safety in httpx/httpcore source (no hang/leak) and confirmed no swallowing, no wedge. Codex's one note (capture the cancel event as a thread arg) is folded in.
  • cancel defaults to None everywhere → default REPL + non-TTY byte-identical. DESIGN §31.15 in the same commit.
  • Live gate pending (maintainer-run): raw-mode Ctrl-C aborting a real stream → ⏹ aborted, indicator clears, next turn works.

In the full-screen app a long turn was a blind wait with no escape. Add a
clean turn-abort: Ctrl-C (a key press now — raw mode disables ISIG, so it
is not a SIGINT) sets a per-turn cancel Event; the Ollama stream checks it
at each read boundary and raises a typed GenerationCancelled, closing the
response in-thread via the stream context manager. The exception is
neither an OllamaError nor an httpx error, so the think-retry and the
transport handler leave it alone and it propagates cleanly.

History stays intact: the model call sits in a try/finally that only
closes the UI response; the reply is recorded after it, so a cancelled
turn skips the record site and never stores a truncated partial — the
user message stands, the aborted reply is discarded. The worker tells a
cancel apart from a failure: GenerationCancelled routes to a clean
abort_turn (clear the thinking indicator, mark the partial '⏹ aborted'),
any other exception is a pane error, and the busy flag clears on every
path so a cancel never wedges the app. The cancel Event is passed to the
worker as a thread argument (a captured local), so the worker never reads
the mutable instance attribute and the loop-thread-only invariant holds.

cancel defaults to None on every hop, so the default REPL and non-TTY
paths are byte-identical. Cancelling a running subprocess (killpg) is a
follow-up; today a cancel during a command aborts the turn once the
command returns. DESIGN.md §31.15 added.
@lavindeep lavindeep merged commit db61e44 into ui-rework Jun 28, 2026
2 checks passed
@lavindeep lavindeep deleted the feat/ui-cancellation branch June 28, 2026 07:45
lavindeep added a commit that referenced this pull request Jul 2, 2026
feat(ui): Ctrl-C cancels a model turn mid-stream
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.

1 participant