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
Since a certain development, somebody overwrote the standard binding ctrl-b for some other default binding. Why? Please, bring the ctrl-b binding. This is a muscle memory binding for anyone using the terminal for a while. This was consistent with all the other readline standard bindings (alt-f, alt-b, ctrl-f, etc.). Find another keybind for the current "background thing". Who thought this was even a good idea to use ctrl-b ?
Thanks for the report, I can reproduce this. I used the latest release, 2.0.26, since the issue only says "V2". In the prompt I typed abc, pressed ctrl+b, then typed X:
Home screen: you get abXc, so ctrl+b moves the cursor as expected.
Inside a session, idle or while a response is running: you get abcX, so ctrl+b does nothing to the cursor.
The cause is that two default keybinds both use ctrl+b: input.move.left (left,ctrl+b) and session.background (ctrl+b). Inside a session the background one takes the key. This still happens on the latest dev (47e5e71). The recording below shows the reproduction, and the script below reproduces it.
Until maintainers decide on the default, you can get ctrl+b back by unbinding the background action in your TUI config (cli.json). In my test this fixed it everywhere, on 2.0.26:
{
"keybinds": {
"session.background": "none"
}
}
You can also give session.background a different key there instead of none.
Related: #51070 / #49216 (the ctrl+b background shortcut itself not firing; #51070 notes the overlap with cursor-left) and #49528 (an earlier, closed PR that removed ctrl+b from the background default).
recording.mp4Reproduction script (drive.ts)
// Repro for anomalyco/opencode#54620: ctrl+b should move the prompt cursor left// (readline backward-char, default `input.move.left: "left,ctrl+b"`), but the// default `session.background: "ctrl+b"` binding takes it.// Expected: typing "abc", ctrl+b, "X" gives "abXc". Bug: it gives "abcX".import{Effect}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"constmk=(ui: {matches: (t: string)=>Effect.Effect<boolean,any>;screenshot: (n?: string)=>Effect.Effect<string,any>})=>(label: string)=>Effect.gen(function*(){constmoved=yield*ui.matches("abXc")constnotMoved=yield*ui.matches("abcX")yield*Effect.log(`[${label}] cursor moved left (abXc): ${moved}; ctrl+b ignored (abcX): ${notMoved}`)yield*ui.screenshot(label)})exportdefaultOpenCodeDriver.use({
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),tui: {recording: true,keypressOverlay: true},},({ ui, llm })=>Effect.gen(function*(){constcheck=mk(ui)// 1. Home screen promptyield*ui.type("abc")yield*ui.press("b",{ctrl: true})yield*ui.type("X")yield*Effect.sleep(500)yield*check("home-prompt")for(leti=0;i<4;i++)yield*ui.press("backspace")// 2. Inside a session, idleyield*llm.queue(Llm.text("First reply."))yield*ui.submit("hello")yield*ui.waitFor("First reply.")yield*Effect.sleep(500)yield*ui.type("abc")yield*ui.press("b",{ctrl: true})yield*ui.type("X")yield*Effect.sleep(500)yield*check("session-idle")for(leti=0;i<4;i++)yield*ui.press("backspace")// 3. Inside a session while a response is runningyield*llm.queue(Llm.text("Thinking"),Llm.pause(6000),Llm.text(" ...done."))yield*ui.submit("second")yield*ui.waitFor("Thinking")yield*ui.type("abc")yield*ui.press("b",{ctrl: true})yield*ui.type("X")yield*Effect.sleep(500)yield*check("session-running")yield*ui.waitFor("done.",{timeout: 15000})}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
Adding measured detail plus a workaround that keeps backgrounding. Verified on 2.0.28, macOS + Ghostty 1.3.1 (TERM=xterm-ghostty).
The bug is scoped to the session view — same binary, same prompt component, same config:
View
carets while typing abcdef, then pressing ctrl+b twice
home composer (no messages yet)
22 → 21 → 20 ✓ (X then lands mid-text: abcXdef)
session view (after ≥1 submitted prompt)
11 → 11 → 11 ✗ nothing happens
Any submitted prompt is enough — an errored turn (Upstream request failed) reproduces it too, so it is not about a response being in flight. Inside a session, session.background (ctrl+b) consumes the key even when the action is a no-op ("Idle requests are a no-op"), so input.move.left (left,ctrl+b) never receives the event.
Workaround that keeps the background shortcut
Instead of unbinding ("none" drops the feature), set fallthrough on the session binding in ~/.config/opencode/cli.json:
Measured in the same session view (config hot-reloaded, no TUI restart):
session.background
ctrl+b in session view
ctrl+b in home composer
default ("ctrl+b")
✗ 11 → 11 → 11
✓
{ "key": "ctrl+b", "fallthrough": true }
✓ 11 → 10 → 9
✓
"ctrl+shift+b"
✓ 11 → 10 → 9
✓
"none"
✓ 11 → 10 → 9
✓
Since a config-level fallthrough: true already produces the intended behaviour, could the default for session.background become { key: "ctrl+b", fallthrough: true } (or its default key be moved, e.g. ctrl+x ctrl+b)? That fixes this without giving up backgrounding, and it lines up with #51070, which makes the prompt-side session.background command fall through to the session route instead of swallowing the event.
Related: #49216 (mirror case: the key is swallowed while a foreground subagent actually runs), #36520.
For reference, Claude Code registers task:background as Ctrl+B, Ctrl+X Ctrl+B in its Task context only, and its editing shortcut table has no Ctrl+B at all — it dropped readline ctrl+b. opencode can do better: it still ships input.move.left, so only the scope/fallthrough handling needs fixing.
I’m looking into restoring readline Ctrl+B by making the default session background binding fall through to cursor movement, as suggested above. I’ll keep the background shortcut and check the shared keymap behavior; this is separate from the prompt command-chain guards in #51070.
Activity
opencode-agent commentedon Oct 11, 2026
Thanks for the report, I can reproduce this. I used the latest release, 2.0.26, since the issue only says "V2". In the prompt I typed
abc, pressedctrl+b, then typedX:abXc, soctrl+bmoves the cursor as expected.abcX, soctrl+bdoes nothing to the cursor.The cause is that two default keybinds both use
ctrl+b:input.move.left(left,ctrl+b) andsession.background(ctrl+b). Inside a session the background one takes the key. This still happens on the latestdev(47e5e71). The recording below shows the reproduction, and the script below reproduces it.Until maintainers decide on the default, you can get
ctrl+bback by unbinding the background action in your TUI config (cli.json). In my test this fixed it everywhere, on 2.0.26:{ "keybinds": { "session.background": "none" } }You can also give
session.backgrounda different key there instead ofnone.Related: #51070 / #49216 (the
ctrl+bbackground shortcut itself not firing; #51070 notes the overlap with cursor-left) and #49528 (an earlier, closed PR that removedctrl+bfrom the background default).recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.tarnish233 commentedon Oct 14, 2026
Adding measured detail plus a workaround that keeps backgrounding. Verified on 2.0.28, macOS + Ghostty 1.3.1 (
TERM=xterm-ghostty).The bug is scoped to the session view — same binary, same prompt component, same config:
abcdef, then pressingctrl+btwiceXthen lands mid-text:abcXdef)Any submitted prompt is enough — an errored turn (
Upstream request failed) reproduces it too, so it is not about a response being in flight. Inside a session,session.background(ctrl+b) consumes the key even when the action is a no-op ("Idle requests are a no-op"), soinput.move.left(left,ctrl+b) never receives the event.Workaround that keeps the background shortcut
Instead of unbinding (
"none"drops the feature), setfallthroughon the session binding in~/.config/opencode/cli.json:{ "keybinds": { "session.background": { "key": "ctrl+b", "fallthrough": true } } }Measured in the same session view (config hot-reloaded, no TUI restart):
session.backgroundctrl+bin session viewctrl+bin home composer"ctrl+b"){ "key": "ctrl+b", "fallthrough": true }"ctrl+shift+b""none"Since a config-level
fallthrough: truealready produces the intended behaviour, could the default forsession.backgroundbecome{ key: "ctrl+b", fallthrough: true }(or its default key be moved, e.g.ctrl+x ctrl+b)? That fixes this without giving up backgrounding, and it lines up with #51070, which makes the prompt-sidesession.backgroundcommand fall through to the session route instead of swallowing the event.Related: #49216 (mirror case: the key is swallowed while a foreground subagent actually runs), #36520.
For reference, Claude Code registers
task:backgroundasCtrl+B, Ctrl+X Ctrl+Bin itsTaskcontext only, and its editing shortcut table has noCtrl+Bat all — it dropped readlinectrl+b. opencode can do better: it still shipsinput.move.left, so only the scope/fallthrough handling needs fixing.Dante-dan commentedon Oct 14, 2026
I’m looking into restoring readline Ctrl+B by making the default session background binding fall through to cursor movement, as suggested above. I’ll keep the background shortcut and check the shared keymap behavior; this is separate from the prompt command-chain guards in #51070.