Skip to content

fix(gastown): Terminal PTY view corrupts intermittently — blacked out rows/columns until TUI redraw #1489

@jrf0110

Description

@jrf0110

Bug

The agent terminal view in the gastown UI intermittently corrupts — sections of the terminal render as blacked out rows/columns. The content is still there (the agent is working normally) but the visual output is broken. Running a command like /status that triggers a full TUI modal redraw clears the corruption.

Symptoms

  • Random rows or columns go black (no content rendered)
  • Happens during normal agent operation (not tied to a specific tool call or event)
  • Affects the xterm.js terminal component in the browser, not the container-side PTY
  • A full TUI redraw (e.g., /status modal, window resize) fixes it immediately
  • Appears to happen more frequently during high output volume (fast streaming, long tool results)

Likely Causes

  1. Dropped or out-of-order PTY data over WebSocket: The terminal output is streamed from the container's PTY via WebSocket to the browser's xterm.js instance. If WebSocket frames are dropped, arrive out of order, or are split at ANSI escape sequence boundaries, xterm.js can get into a state where its internal screen buffer is inconsistent with what the agent's TUI thinks is rendered. The TUI (opencode TUI built on @opentui/solid) uses differential rendering — it only sends the escape sequences needed to update changed cells. If a differential update is lost, subsequent updates are applied to a stale screen buffer, producing visual corruption.

  2. xterm.js buffer overflow or resize race: If the terminal is resized while output is streaming, or if the output buffer fills faster than xterm.js can render, frames can be dropped or misapplied. The PTY WebSocket proxy in the container (control-server.ts PTY connect handler) may not be handling backpressure correctly.

  3. ANSI escape sequence split across WebSocket frames: If a multi-byte ANSI escape sequence (e.g., cursor movement, color change) is split across two WebSocket messages, xterm.js may interpret the partial sequence incorrectly, corrupting the parser state. All subsequent output renders wrong until a full-screen redraw resets the parser.

Fix Options

  1. Periodic full redraw: Have the container-side TUI send a full screen redraw (ANSI reset + full repaint) every N seconds or after N differential updates. This is a brute-force fix but guarantees recovery within a bounded time.

  2. WebSocket reliability: Ensure the PTY WebSocket proxy buffers and delivers frames atomically with respect to ANSI escape sequences. Never split a frame mid-escape-sequence.

  3. Client-side corruption detection: In the xterm.js integration, detect when large regions of the screen are blank (heuristic: >30% of visible cells are empty when they shouldn't be) and request a full redraw from the container.

  4. Resize-safe streaming: Pause PTY output during resize events and flush after the resize is acknowledged by both xterm.js and the container-side PTY.

Workaround

Type /status or any command that triggers a full TUI modal redraw. Resizing the browser window may also help (triggers a resize event → full repaint).

Files

  • Browser-side xterm.js terminal component (likely in src/app/(app)/gastown/[townId]/ or a shared component)
  • container/src/control-server.ts — PTY WebSocket handler
  • The opencode TUI renderer in packages/opencode/src/cli/cmd/tui/ (upstream, differential rendering logic)

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Should fix before soft launchbugSomething isn't workinggt:containerContainer management, agent processes, SDK, heartbeatgt:uiDashboard, settings, terminal, drawerskilo-duplicateAuto-generated label by Kilokilo-triagedAuto-generated label by Kilo

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions