Repository navigation
feat(tui): interrupt the agent with ctrl+c - #108
Merged
Merged
Conversation
While a turn is running, ctrl+c now aborts it on a single press and the
prompt footer leads with "ctrl+c interrupt" instead of "esc interrupt".
Escape keeps its existing double-press interrupt.
Added a session_interrupt_now keybind (default ctrl+c) and its
session.interrupt.now command rather than adding ctrl+c to
session_interrupt, since the two need different press counts and
inspecting the triggering key inside one handler breaks on rebind.
Precedence runs through `enabled` predicates rather than binding order,
which is not stable (same-priority layers resolve by registration order,
and useBindings re-registers on every reactive change):
- session.interrupt.now requires a busy session and an empty prompt, so
ctrl+c falls through to prompt.clear with a draft present and to
app.exit.confirm while idle.
- app_exit_confirm moves to its own binding layer that also requires the
session to be idle, so ctrl+c cannot arm the double-press quit mid-turn.
app_exit (ctrl+d, <leader>q) keeps the shared predicate and still quits
immediately.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
quickbeard
added a commit
that referenced
this pull request
Jul 20, 2026
Restores #108's entry to what merged (plus the PR link it was missing) and adds a separate bullet for this change, per the convention that follow-ups get their own entry rather than rewriting the earlier one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
quickbeard
added a commit
that referenced
this pull request
Jul 20, 2026
* fix(tui): advertise the esc interrupt in the busy footer #108 made the busy prompt footer lead with "ctrl+c interrupt". Put "esc interrupt" back in front: escape is the interrupt we advertise, in the footer and in the home screen tip. Ctrl+C keeps the single-press interrupt from #108 and every precedence rule around it — only what is on show changes. The footer shortcut now resolves session.interrupt (mapping escape -> esc) and falls back to the session.interrupt.now label when escape is unbound, the mirror of the fallback #108 introduced. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: give the esc-interrupt revert its own changelog entry Restores #108's entry to what merged (plus the PR link it was missing) and adds a separate bullet for this change, per the convention that follow-ups get their own entry rather than rewriting the earlier one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: fold the esc-interrupt revert into the ctrl+c changelog entry Both PRs land in the same unreleased version, and #109 walks back part of #108, so two bullets would ship contradicting each other. One entry describes the net behavior and cites both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
While a turn is running,
Ctrl+Cnow aborts it on a single press, and the prompt footer leads withctrl+c interruptinstead ofesc interrupt.escapekeeps its existing double-press interrupt, so nobody's muscle memory breaks.Behavior
ctrl+cWhile busy,
Ctrl+Ccan no longer arm the double-press quit from #51.Ctrl+D,<leader>q, and/exitstill quit immediately, including mid-turn.Approach
Added a
session_interrupt_nowkeybind (defaultctrl+c) and itssession.interrupt.nowcommand rather than addingctrl+ctosession_interrupt— the two need different press counts, and inspecting the triggering key inside one handler breaks as soon as someone rebinds.Precedence runs through
enabledpredicates rather than binding order. Order is not a stable disambiguator here: same-priority layers resolve by registration order, anduseBindingsre-registers a layer on every reactive change, which bumps it to newest.session.interrupt.nowrequires a busy session and an empty prompt, soctrl+cfalls through toprompt.clearwith a draft present and toapp.exit.confirmwhile idle.app_exit_confirmmoves to its own binding layer that also requires the session to be idle.app_exitkeeps the original predicate.Notes
The home screen's "stop the AI mid-response" tip now points at
session.interrupt.now, so it agrees with the footer.Permission and question prompts keep
ctrl+cas their scoped reject — unchanged.The interactive split-footer mode (
codev --mini, and--mini --attach) has its own separate two-press ctrl+c logic; left alone, it's an independent job. Plain non-interactivecodev run "…"is unaffected either way — it builds no renderer, keymap, or footer, and ctrl+c there is an ordinary SIGINT.Adding a keybind driftspackages/sdk/js/src/gen/types.gen.ts, which is not regenerated here — following the precedent set by feat(tui): require double Ctrl+C to quit #51, which addedapp_exit_confirmthe same way.Correction, added after merge: that claim was wrong. Nothing regenerates
src/gen—packages/sdk/js/script/build.tsoutputs tosrc/v2/genand merely runs prettier oversrc/gen(line 115). The v2 output contains no keybind types at all, and a freshly generated spec (bun dev generate) has no keybind schema and nokeybindsproperty onConfig, because keybinds were moved out of the server config:packages/opencode/src/config/config.ts:54-57deleteskeybindsas a legacy key andconfig/tui-migrate.tsmigrates it into the TUI's own config.So
KeybindsConfig(src/gen/types.gen.ts:772) andConfig.keybinds(line 1184) are fossils of a pre-migration API. This PR does not drift them — regenerating correctly would delete them, which is a separate, breaking cleanup unrelated to this change. The schema that actually validates keybinds (packages/tui/src/config/index.tsx:56, built fromTuiKeybind.KeybindOverrides) derives fromDefinitionsat runtime and already knows aboutsession_interrupt_now.Testing
packages/tui/test/interrupt.view.test.tsxboots the real app against a session the fixture server reports as busy, then drives actualctrl+ckey events: draft cleared with no abort, then abort on a single press, with the exit confirm verified disarmed.bun typecheckclean across all 31 packages.app.exit prints the session epilogue after scoped cleanup) is pre-existing and fails identically on an unmodified tree.Docs (en + es),
DIVERGENCES.md, andCHANGELOG.mdupdated.🤖 Generated with Claude Code