You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE]: Double Esc right after sending should undo the message and restore it to the input #52537
I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
In Claude Code, if you hit Esc right after sending a message, the turn stops, the message is removed from the conversation, and its text goes back into the input so you can fix it and resend.
In opencode, double Esc only interrupts. The half-sent message stays in the transcript and the text is gone from the input, so you have to interrupt, then /undo, every time you notice a typo or a missing detail a second after hitting enter.
Proposal: if you press Esc twice within ~2s of sending, abort + revert that message and put it back in the prompt (attachments included). Outside that window, Esc behaves exactly as today.
There was a PR for this before (#36538) that got closed by the stale-PR cleanup, not on merit.
Thanks for the suggestion! As I understand it, you'd like pressing Esc twice within a couple of seconds of sending to abort the turn, remove that message from the conversation and put its text and attachments back in the input, so you don't have to interrupt and then /undo every time you spot a typo. Outside that window, Esc would work as it does now.
This isn't something a plugin can do yet. TUI plugins can add keybinds and commands, but they can't read or set what's in the prompt input, so there's no way to restore the message there. I've noted the needed plugin API addition for the maintainers.
Adding measurements that may help whoever implements this — not a competing request.
The gesture is still unbound as of the v2.0.32 source tree I have checked out: there is no timed two-press / double-Escape handling anywhere in packages/tui/src or packages/app/src (searching double-esc / double-tap / two-press finds only the mini-mode ctrl+c exit hint). session.interrupt owns the single escape binding, and a keymap sequence cannot express a double tap without swallowing the first press.
The surface already ships, which is why this looks like a gesture-only gap: session.timeline renders the user's own messages (searchable, select → Revert/Copy) via dialog-timeline.tsx, and /undo stages the rollback + restores the prompt to the composer.
FWIW this is expressible today with no core change, as a TUI plugin: listen on context.renderer.keyInput"keypress", detect a second escape within ~800 ms, then context.keymap.dispatch("session.timeline") (or kick off a revert). Listening on the renderer key stream does not consume the event, so single-Esc interrupt is preserved by construction. I have this implemented locally and unit-tested off the compiled plugin (window boundary, session gating, pair-reset, cleanup); I have not yet confirmed it in a live TUI. Happy to share the plugin if the approach is of interest.
It's an opencode TUI plugin, not a fork — the stock binary is untouched:
double-tap Escape → context.keymap.dispatch("session.timeline"), i.e. opencode's own timeline dialog (the searchable list of your sent messages, select → Revert/Copy). Nothing is reimplemented.
The listener is on the raw context.renderer.keyInput "keypress" stream, which does not consume the event, so single-Escape interrupt/close behaviour is preserved by construction. A keymap sequence couldn't do this without swallowing the first press, which is why it's a timing check (800 ms) rather than a binding.
It ships a no-op index.ts server half, because a directory under plugins/ is a target for both the TUI loader and the server loader; without <dir>/index the server raises Plugin entrypoint not found.
Install (no npm publish yet — that's pending): either a nix flake (imports = [ inputs.opencode-double-esc-timeline.homeManagerModules.default ]) or copying tui.ts + index.ts into ~/.config/opencode/plugins/opencode-double-esc-timeline/.
Verified working on opencode v2.0.32. If the gesture gets bound natively (and #14037's closure implies someone thought it was), this plugin can just be deleted — it only adds the gesture, it doesn't own any state.
Activity
github-actions commentedon Oct 1, 2026
This issue might be a duplicate of existing issues. Please check:
For keybind-related issues, please also check our pinned keybinds documentation: #4997
opencodejr commentedon Oct 28, 2026
Thanks for the suggestion! As I understand it, you'd like pressing Esc twice within a couple of seconds of sending to abort the turn, remove that message from the conversation and put its text and attachments back in the input, so you don't have to interrupt and then
/undoevery time you spot a typo. Outside that window, Esc would work as it does now.This isn't something a plugin can do yet. TUI plugins can add keybinds and commands, but they can't read or set what's in the prompt input, so there's no way to restore the message there. I've noted the needed plugin API addition for the maintainers.
Related:
The maintainers will take a look and consider it.
ZacxDev commentedon Nov 7, 2026
Adding measurements that may help whoever implements this — not a competing request.
The gesture is still unbound as of the v2.0.32 source tree I have checked out: there is no timed two-press / double-Escape handling anywhere in
packages/tui/srcorpackages/app/src(searchingdouble-esc/double-tap/two-pressfinds only the mini-modectrl+cexit hint).session.interruptowns the singleescapebinding, and a keymap sequence cannot express a double tap without swallowing the first press.The surface already ships, which is why this looks like a gesture-only gap:
session.timelinerenders the user's own messages (searchable, select → Revert/Copy) viadialog-timeline.tsx, and/undostages the rollback + restores the prompt to the composer.Two trackers worth knowing about:
completed, but no such binding exists — it was closed by the automation, not by a shipping change. Same shape as the note in the original post about feat(tui): undo recent prompt with double escape #36538.FWIW this is expressible today with no core change, as a TUI plugin: listen on
context.renderer.keyInput"keypress", detect a secondescapewithin ~800 ms, thencontext.keymap.dispatch("session.timeline")(or kick off a revert). Listening on the renderer key stream does not consume the event, so single-Esc interrupt is preserved by construction. I have this implemented locally and unit-tested off the compiled plugin (window boundary, session gating, pair-reset, cleanup); I have not yet confirmed it in a live TUI. Happy to share the plugin if the approach is of interest.ZacxDev commentedon Nov 7, 2026
Follow-up: we ended up solving this for ourselves, and it's now public in case it's useful here.
https://github.com/ZacxDev/opencode-double-esc-timeline (MIT)
It's an opencode TUI plugin, not a fork — the stock binary is untouched:
Escape→context.keymap.dispatch("session.timeline"), i.e. opencode's own timeline dialog (the searchable list of your sent messages, select → Revert/Copy). Nothing is reimplemented.context.renderer.keyInput"keypress" stream, which does not consume the event, so single-Escapeinterrupt/close behaviour is preserved by construction. A keymap sequence couldn't do this without swallowing the first press, which is why it's a timing check (800 ms) rather than a binding.index.tsserver half, because a directory underplugins/is a target for both the TUI loader and the server loader; without<dir>/indexthe server raisesPlugin entrypoint not found.Install (no npm publish yet — that's pending): either a nix flake (
imports = [ inputs.opencode-double-esc-timeline.homeManagerModules.default ]) or copyingtui.ts+index.tsinto~/.config/opencode/plugins/opencode-double-esc-timeline/.Verified working on opencode v2.0.32. If the gesture gets bound natively (and #14037's closure implies someone thought it was), this plugin can just be deleted — it only adds the gesture, it doesn't own any state.