feat(tui): surface worker RPC failures instead of hanging on a blank scr - #42885
Open
anotheroni wants to merge 2 commits into
Open
feat(tui): surface worker RPC failures instead of hanging on a blank scr#42885anotheroni wants to merge 2 commits into
anotheroni wants to merge 2 commits into
Conversation
…screen Covered by new tests in test/util/rpc.test.ts. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Reviewed-by: GPT-5.6 Sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Covered by new tests in test/util/rpc.test.ts.
Reviewed-by: GPT-5.6 Sol
Issue for this PR
Closes #34981
Related: #41284, #35494
Type of change
What does this PR do?
Previously a throwing RPC handler, a crashed/killed worker, or an
unhandled rejection after the TUI entered alt-screen/raw mode all left
the process hanging on a blank terminal with nothing printed and no
useful exit code.
client() rejects all in-flight and future calls when the worker
errors, exits unexpectedly, or sends an undeserializable message;
a postMessage() throw only rejects that one call. Adds
onDisconnect() for callers with nothing in flight, and
expectDisconnect() to distinguish our own worker.terminate() from a
real crash.
preserves a non-zero exit code instead of always exiting 0.
mode is active, restores the terminal, and reports the error.
How did you verify your code works?
Ran opencode with a bad database to verify that I get an error message instead of a blank screen
18 tests in rpc.test.ts covering throwing handlers, worker error/close/
messageerror events, postMessage failures, and (not just a synthetic
EventTarget) a real Bun Worker that crashes on import. Plus 1 new test
in the tui package's app-lifecycle.test.tsx. See the Tests section below
for what each one covers.
Cross-checked against a previous attempt at this same bug (#34974, closed,
never merged) to see if it covered something that this patch didn't.
Tests
Process-level fatal handling (unhandledRejection/uncaughtException)
is only partially covered: the app-lifecycle.test.tsx test invokes
the triggerFatal() callback directly rather than emitting a real
unhandledRejection or uncaughtException event.
packages/opencode/test/util/rpc.test.ts:listen()replies with anrpc.resulterror instead of leaving the request to hang.Proxy) still produces a safe fallback error string, rather than
throwing again inside the error path itself.
errorset causesclient.call()to reject rather than resolve.
client.call()normally, as a baseline against the error-path tests above.
target.postMessage()rejectsthe call immediately instead of leaving it pending forever.
errorevent rejects any currently in-flightcall.
real Bun
Worker(not a mocked EventTarget) running a fixture thatthrows on import, to confirm the mocked tests reflect real behavior.
closeevent rejects in-flight calls with an"exited unexpectedly" error.
messageerrorevent (an undeserializable message)rejects in-flight calls.
dataisn't valid JSON rejects in-flightcalls instead of throwing uncaught inside
onmessage.typefield rejectsin-flight calls.
typerejects in-flightcalls.
immediately instead of queueing forever.
onDisconnectfires on a crash even when no call iscurrently pending to reject.
onDisconnectdoes not fire whenworker.terminate()follows a call to
expectDisconnect(), i.e. our own intentionalshutdown isn't reported as a crash.
expectDisconnect()was called first, since a clean-looking exitcode doesn't distinguish our own shutdown from an unrelated one.
it, even if
expectDisconnect()is called moments later — guardsagainst
expectDisconnect()being called too early and swallowing areal crash.
onDisconnectafter the workerhas already died is invoked immediately rather than missed.
packages/tui/test/app-lifecycle.test.tsx:dispose()call during cleanup) is still printed to stderr and setsexit code 1, instead of being lost once teardown has already started.
Screenshots / recordings
Reproduced against a real corrupted
opencode.db(a bad"dummy"value wherea session ID was expected). Before this fix, the terminal just sat blank forever
with nothing printed. After:
Matching server log line:
Checklist