Skip to content

TUI hangs on default port — works with --port flag #37060

Closed as not planned
Closed as not planned

Description

@Zireael

Description

The TUI hangs indefinitely after the server reaches init. The server process completes initialization successfully, but the TUI client never renders the interactive UI. This occurs in both TUI mode and attach mode.

Environment

  • OS: Windows 10.0.26200 x64
  • opencode versions tested: 1.17.19, 1.18.1 (both exhibit the same behavior)
  • Terminals tested: Git Bash (MSYS2), Windows Terminal
  • Installation: bun global install

Reproduction

The issue reproduces with zero config, zero plugins, no LSPs, and a fresh database:

# All of these hang at "init":
opencode                          # TUI mode
opencode attach http://127.0.0.1:4096  # attach to running server
opencode --pure                   # no plugins
opencode --mini                   # minimal TUI
# These all work perfectly:
opencode run "hello"              # headless — creates session, completes
opencode serve                    # headless server — binds port, responds HTTP
opencode web                      # web UI — opens browser, fully functional

Evidence

Log output (hangs at init)

timestamp=2026-07-15T06:44:03.179Z level=INFO run=f235a4ff message="all LSPs are disabled"
timestamp=2026-07-15T06:44:03.179Z level=INFO run=f235a4ff message="all formatters are disabled"
timestamp=2026-07-15T06:44:03.179Z level=INFO run=f235a4ff message=init
# ← nothing after this, TUI never renders

Terminal escape codes emitted before hang

The TUI sends terminal capability queries and blocks:

[?2031h]10;?]11;?[>0q[?25l[s[6nP+q4d73\
[?1016$p[?2027$p[?2031$p[?1004$p[?2004$p[?2026$p[?u
]99;i=opentui-notifications:p=?;\]1337;Capabilities\
[H]66;w=1; \[6nH]66;s=2; \[6n[u[s
[?1049h[>4;1m[?2027h[?2004h[?1000h[?1002h[?1003h[?1006h[14t

These are DECRPM (mode query) requests. The TUI appears to block waiting for terminal responses that never arrive.

What works vs what hangs

Mode Result
opencode run "hello" Creates session, runs prompt, completes
opencode serve Binds port, serves HTTP 200
opencode web Opens browser, fully functional
opencode (TUI) Hangs at init — never renders
opencode attach Hangs at init — same escape codes, same hang

Clean environment test

I verified the issue persists with:

  • Fresh config directory (~/.config/opencode/ empty)
  • Fresh database (moved opencode.db to _old_db)
  • Fresh state (~/.local/state/opencode/ removed)
  • --pure flag (no plugins)
  • Both versions 1.17.19 and 1.18.1

Diagnostics

$ opencode debug info
opencode version: 1.18.1
os: Windows_NT 10.0.26200 x64
terminal: xterm-256color

$ opencode debug startup
863.1158

Hypothesis

The opentui library sends DECRPM terminal capability queries ([?NNNN$p) during initialization and blocks waiting for responses. On my Windows system, the terminal (or the terminal emulation layer in the opentui runtime) never responds to these queries, causing a deadlock.

This may be related to:

  • Bun runtime on Windows not properly emulating terminal responses
  • A missing timeout on the opentui terminal query mechanism
  • A regression in how the TUI client communicates with the embedded server after init

Workaround

opencode web provides full functionality via browser.

Activity

  1. Zireael commented on Jul 15, 2026

    @Zireael
    Author

    Update: --port flag workaround

    Specifying an explicit --port resolves the hang:

    # HANGS (default port):
    opencode
    
    # WORKS (explicit port):
    opencode --port 4521

    This changes the diagnosis. The TUI is not deadlocking on terminal capability queries. Instead, the issue is likely a race condition in the default port binding/communication path between the TUI client and the embedded server.

    How the default flow likely works (and breaks)

    Without --port:

    1. TUI process starts, emits terminal escape codes
    2. TUI spawns embedded server process
    3. Server needs to pick a port, bind, and communicate the port back to TUI
    4. Somewhere in step 3-4, the handshake fails silently — server reaches init but TUI never receives the port/connection info

    With --port:

    1. TUI process starts, emits terminal escape codes
    2. TUI knows the port upfront (no discovery needed)
    3. Server binds to the specified port
    4. TUI connects directly — handshake succeeds

    Reproduction with explicit port

    # All of these still hang:
    opencode
    opencode --pure
    opencode attach http://127.0.0.1:4096
    
    # These work:
    opencode --port 4521
    opencode run "hello"
    opencode serve
    opencode web

    This suggests the bug is in the embedded server startup path when no port is specified — specifically in how the server communicates its chosen port back to the TUI client process.

  2. changed the title [-]TUI hangs at init on Windows — terminal capability query deadlock[/-] [+]TUI hangs on default port — works with --port flag[/+] on Jul 15, 2026
  3. Zireael commented on Jul 15, 2026

    @Zireael
    Author

    Root Cause Analysis — Source Code Evidence

    The bug: in-process Worker RPC path has no timeout, blocks on service graph initialization

    When opencode is run without --port, the TUI uses an in-process Worker RPC mechanism to communicate with the server. This path has no timeout, so if the Worker blocks during service initialization, the TUI hangs forever.

    Code flow (default path, no --port)

    1. Transport decision — packages/opencode/src/cli/cmd/tui.ts:234

    const external = hasArg("--port") || hasArg("--hostname") || network.mdns === true

    Without --port, external = false → uses in-process Worker RPC.

    2. Worker fetch proxy — packages/opencode/src/cli/cmd/tui.ts:24-39

    function createWorkerFetch(client: RpcClient): typeof fetch {
      const fn = async (input, init) => {
        const result = await client.call("fetch", { ... })  // ← NO TIMEOUT
        return new Response(result.body, { status: result.status, headers: result.headers })
      }
      return fn
    }

    3. Worker RPC handler — packages/opencode/src/cli/tui/worker.ts:31-49

    async fetch(input) {
      const response = await Server.Default().app.fetch(request)  // ← BLOCKS HERE
      ...
    }

    4. Server.Default() is lazy — packages/opencode/src/server/server.ts:56-65

    export const Default = lazy(() => {
      const handler = HttpApiApp.webHandler().handler  // ← builds ~70 services
      ...
    })

    5. HttpApiApp.webHandler() builds the entire service graph — packages/opencode/src/server/routes/instance/httpapi/server.ts:1-60+

    This initializes ~70 Effect services: Database, LSP, MCP, Provider, Session, ModelsDev (network call), Npm, etc.

    6. RPC has NO timeout — packages/opencode/src/util/rpc.ts:45-50

    call(method, input) {
      return new Promise((resolve) => {
        pending.set(requestId, resolve)
        target.postMessage(...)  // ← fires and forgets, waits forever
      })
    }

    7. Worker swallows errors — packages/opencode/src/cli/tui/worker.ts:16-21

    const onUnhandledRejection = (_error: unknown) => {}
    const onUncaughtException = (_error: Error) => {}
    process.on("unhandledRejection", onUnhandledRejection)
    process.on("uncaughtException", onUncaughtException)

    Why --port fixes it

    When --port is specified (tui.ts:238-244):

    const transport = external
      ? { url: (await client.call("server", network)).url, ... }  // real HTTP server
      : { url: "http://opencode.internal", fetch: createWorkerFetch(client), ... }

    The Worker starts a real HTTP server via Server.listen(), and the TUI connects via real HTTP fetch (with built-in timeouts and retry logic) instead of the timeout-free RPC proxy.

    Likely trigger: ModelsDev network call

    The ModelsDev service (imported in server.ts:58) fetches model definitions from models.dev. We observed this in logs:

    "Failed to fetch models.dev" cause="HttpClientError: Transport error"
    

    If this network call hangs instead of failing quickly (e.g., DNS timeout on Windows can be 21+ seconds, or a firewall silently dropping packets), it blocks the entire service graph initialization → Worker fetch() blocks → RPC blocks forever → TUI hangs.

    Key files

    File Role
    packages/opencode/src/cli/cmd/tui.ts:234 Transport decision (hasArg("--port"))
    packages/opencode/src/cli/cmd/tui.ts:24-39 createWorkerFetch — no timeout
    packages/opencode/src/cli/tui/worker.ts:31-49 Worker fetch() handler → Server.Default()
    packages/opencode/src/server/server.ts:56-65 Default lazy init → HttpApiApp.webHandler()
    packages/opencode/src/server/routes/instance/httpapi/server.ts:1-60 Service graph (~70 services)
    packages/opencode/src/util/rpc.ts:45-50 RPC call — no timeout, no error propagation
    packages/opencode/src/cli/tui/worker.ts:16-21 Error handlers swallowed

    Suggested fixes

    1. Add timeout to Server.Default() lazy initialization — if the service graph doesn't initialize within N seconds, fail gracefully
    2. Add timeout to RPC call() — reject the promise after a timeout instead of hanging forever
    3. Don't swallow errors in Worker — propagate unhandledRejection so blocked initialization surfaces
    4. Add timeout to ModelsDev network call — ensure DNS/connect failures don't hang the entire init chain
  4. github-actions commented on Sep 15, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions