fix(opencode): answer a failed TUI worker request instead of hanging

Merged
NO
9d ago
+23-12

Issue for this PR

Closes #54934

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The TUI talks to its server worker over a small RPC channel and waits on it before it draws anything, for example to start the listener for --port. When the worker's handler threw, Rpc.listen never posted a reply (the worker's unhandledRejection handler swallows the error), so the TUI's client.call() never settled. With opencode --port 4096 and the port already taken, that is a blank terminal forever, with no hint why.

Now Rpc.listen answers a failed request with the error's message and Rpc.client rejects the pending call with it. The failure reaches the TUI's existing error handling, and opencode exits with Failed to start server. Is port 4096 in use?. Server.listen rejects with a ServeError that has no message of its own and names the port in its cause, so when an error has no message, the cause's is sent instead.

Related: #34981 and #42885 cover a worker that dies with calls pending. This covers a worker that stays up but whose handler throws.

How did you verify your code works?

  • New test/util/rpc.test.ts drives a real worker whose methods return, throw, reject, and throw with only a cause. On v1 without the change, 3 of the 4 tests fail because those calls get no reply; with it, all 4 pass.
  • opencode --port <port> while another process holds the port: before, the screen is still blank 20 s later; after, opencode exits with code 1 and the message above.
  • bun typecheck in packages/opencode is clean, and test/cli/tui/thread.test.ts passes.

Screenshots / recordings

Not a UI change; the visible difference is the error message above.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

This PR is AI generated, but under direct supervision and on request of @Nowaker.