Skip to content

tui: cursor.style block crashes in cursorStyle (e.color undefined) #58992

Closed

Description

@maxmad64bis

Summary

Setting cursor.style to "block" (with cursor.blinking: true) in ~/.config/opencode/cli.json crashes the TUI with TypeError: undefined is not an object (evaluating 'e.color') inside cursorStyle. Removing the cursor override stops the crash; cursor.style: "line" worked.

Environment

  • opencode version: 2.1.4
  • OS: Ubuntu 24.04 on WSL2, kernel 6.18.33.2-microsoft-standard-WSL2 (x86_64)
  • Terminal: Windows Terminal Preview 1.26; inside WSL TERM=xterm-256color, TERM_PROGRAM and COLORTERM are unset
  • Shell: /bin/bash
  • Install/channel: npm global via nvm (@opencode/cli 2.1.4), release
  • Active plugins: skill-guard (local), omniroute-opencode-plugin-v2 (local)

Reproduction

  1. In ~/.config/opencode/cli.json, set:
    "cursor": { "style": "block", "blinking": true }
    The previous value was "style": "line", which did not crash.
  2. Let the TUI hot-reload the config (no restart was needed).
  3. The TUI crashes shortly afterward, on a cursor render/update.

Removing the cursor block and hot-reloading restores the TUI.

Frequency: observed once; I reverted the setting to avoid another crash, so I did not re-test. The crash appeared within ~1-2 minutes of the change.

Expected Behavior

The block cursor should render without crashing.

Actual Behavior

TypeError: undefined is not an object (evaluating 'e.color')
    at cursorStyle (/$bunfs/root/chunk-sm21dtbc.js:187:2076)
    at setProperty (/$bunfs/root/chunk-k8fpjnkk.js:3:8290)
    at setProp (/$bunfs/root/chunk-k8fpjnkk.js:3:4269)
    at <anonymous> (/$bunfs/root/chunk-4r2qsk0b.js:36:4956)
    at Fe (/$bunfs/root/chunk-aaw6zawx.js:2:9876)
    at K (/$bunfs/root/chunk-aaw6zawx.js:2:9657)
    at Z (/$bunfs/root/chunk-aaw6zawx.js:2:11342)
    at xt (/$bunfs/root/chunk-aaw6zawx.js:2:12450)
    at j (/$bunfs/root/chunk-aaw6zawx.js:2:11501)
    at wt (/$bunfs/root/chunk-aaw6zawx.js:2:12148)

Additional Context

  • The config at crash time also had animations: false, session.scrollbar: true, session.tps: true. The only cursor-related change was style: "line" -> "block" with blinking: true.
  • Workaround: leave cursor unset (terminal default) or keep "style": "line". The current config has no cursor block and the TUI is stable.
  • cursorStyle appears to assume e.color exists; with the default dark theme and a block cursor it is undefined. A fallback to the theme cursor/text color (or a guard) would avoid the crash.
  • The crash stack is from the TUI crash screen. I did not find a matching entry in ~/.local/share/opencode/log/opencode.log.

Activity

  1. opencode-agent commented on Oct 25, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report! I reproduced this crash on 2.1.4, with the same undefined is not an object (evaluating 'e.color') error and the same stack. It still happens on the latest dev (15646867).

    In my tests, block itself is not the trigger. The crash happens when a hot reload changes cursor from set to unset. Steps:

    1. Start the TUI with "cursor": { "style": "block", "blinking": true } in cli.json.
    2. Remove the cursor block from cli.json while the TUI is running.
    3. The TUI crashes as soon as the reload is applied.

    Switching from line to block, or starting with a block cursor or with no cursor setting at all, did not crash for me. The TUI passes the unset setting straight to the input's cursor style, and the code there doesn't handle a missing value.

    My guess for your case: an editor save may have left cli.json briefly incomplete or invalid. The TUI then drops the whole config on reload (see #56318), so cursor is unset for that moment. I haven't confirmed this.

    Until it is fixed, avoid changing or removing the cursor setting while the TUI is running. Restart after editing it instead.

    The recording and screenshot below show the crash, and the script below reproduces it.

    Related: #56318 (one invalid cli.json field discards the whole config), #43521 (cursor style ignored in --mini).

    recording.mp4

    Screenshot

    Reproduction script (drive.ts)
    // Repro for #58992: with a `cursor` block in cli.json, a hot reload that changes/removes it
    // while a dialog with a text input (e.g. the ctrl+p command palette) is open crashes the TUI
    // in the @opentui `cursorStyle` setter (`undefined is not an object (evaluating 'e.color')`).
    import { Effect } from "effect"
    import { OpenCodeDriver } from "opencode-drive"
    import { readFileSync, writeFileSync } from "node:fs"
    import { join } from "node:path"
    
    const text = (f: any) => f.lines.map((l: any) => l.spans.map((s: any) => s.text).join("")).join("\n")
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
        tuiConfig: { cursor: { style: "block", blinking: true } },
      } as any,
      ({ ui, artifacts }) =>
        Effect.gen(function* () {
          const cfg = join(artifacts, "files", ".opencode", "cli.json")
          yield* Effect.sleep("3 seconds")
          yield* ui.press("p", { ctrl: true })
          yield* Effect.sleep("2 seconds")
          yield* ui.screenshot("palette-open")
          const json = JSON.parse(readFileSync(cfg, "utf8"))
          delete json.cursor
          writeFileSync(cfg, JSON.stringify(json, null, 2))
          yield* Effect.log("cli.json now: " + readFileSync(cfg, "utf8"))
          yield* Effect.sleep("5 seconds")
          yield* ui.screenshot("after-reload")
          yield* Effect.log(text(yield* ui.capture()))
        }),
    )

    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. tneemo commented on Oct 26, 2026

    @tneemo

    I'd like to take this one. The crash comes from resolve() returning cursor: undefined after a reload, which the inputs then pass straight to OpenTUI's cursorStyle setter. I have a fix that resolves an unset cursor to OpenTUI's default (block, blinking) and makes mounted inputs pick up the reloaded value, plus a regression test that mounts DialogPrompt and reproduces the same stack. I can open the PR if that works for you.

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