Skip to content

Standard readline binding CTRL-B BROKEN #54620

Open

Description

@sim590

Description

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 ?

Plugins

No response

OpenCode version

V2

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Activity

  1. opencode-agent commented on Oct 11, 2026

    @opencode-agent
    Contributor

    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.mp4
    Reproduction 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"
    
    const mk = (ui: { matches: (t: string) => Effect.Effect<boolean, any>; screenshot: (n?: string) => Effect.Effect<string, any> }) => (label: string) =>
      Effect.gen(function* () {
        const moved = yield* ui.matches("abXc")
        const notMoved = yield* ui.matches("abcX")
        yield* Effect.log(`[${label}] cursor moved left (abXc): ${moved}; ctrl+b ignored (abcX): ${notMoved}`)
        yield* ui.screenshot(label)
      })
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true, keypressOverlay: true },
      },
      ({ ui, llm }) =>
        Effect.gen(function* () {
          const check = mk(ui)
          // 1. Home screen prompt
          yield* ui.type("abc")
          yield* ui.press("b", { ctrl: true })
          yield* ui.type("X")
          yield* Effect.sleep(500)
          yield* check("home-prompt")
          for (let i = 0; i < 4; i++) yield* ui.press("backspace")
    
          // 2. Inside a session, idle
          yield* 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 (let i = 0; i < 4; i++) yield* ui.press("backspace")
    
          // 3. Inside a session while a response is running
          yield* 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.

  2. tarnish233 commented on Oct 14, 2026

    @tarnish233

    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:

    {
      "keybinds": {
        "session.background": { "key": "ctrl+b", "fallthrough": true }
      }
    }

    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.

  3. Dante-dan commented on Oct 14, 2026

    @Dante-dan
    Contributor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions