Repository navigation
Description
Description
After pressing Enter to send a prompt to the agent, the entire TUI goes black while the agent is working. The process is still alive (keyboard input works, modals render correctly on top of the black screen). Switching to another terminal tab and back restores the display. This happens consistently (every session, every prompt), not intermittently.
OpenCode version: 1.18.3
Platform: Arch Linux
Terminal: (add your terminal info)
Steps to reproduce
- Run
opencodein any project - Enter any prompt and press Enter
- As the agent starts working, the screen goes black
- Switch to another terminal tab and back — display recovers
- Prompt again → screen goes black again
Root cause
The bug is in the bundled @opentui/core CliRenderer render loop. A race condition in the frame scheduling mechanism causes the render loop to silently stop producing frames during bursts of requestRender() calls.
The race condition in detail
The renderer operates in idle mode (_isRunning = false), where frames are only produced on demand via requestRender(). The flow is:
- User presses Enter → burst of events → multiple
requestRender()calls - First
requestRender()passes the guards and callsactivateFrame()→loop() loop()setsthis.rendering = true- During
loop(), subsequentrequestRender()calls hit thethis.renderingguard (line 1516) and only setthis.immediateRerenderRequested = true loop()finishes, readsimmediateRerenderRequested(line 4478), schedules another loop viasetTimeout, and clears the flag (line 4481)- RACE: Between line 4481 (flag cleared) and line 4516 (
this.rendering = falseinfinally), anotherrequestRender()fires —this.renderingis stilltrue→ setsimmediateRerenderRequested = trueagain - The
finallyblock (lines 4515–4521) setsthis.rendering = falseand callsresolveIdleIfNeeded()— but does NOT checkimmediateRerenderRequested - Control returns to
activateFrame(), which clearsupdateScheduled = falsein itsfinally - The scheduled
setTimeoutfrom step 5 fires, callsloop()— if no newrequestRender()arrives in time,_isRunningisfalseandimmediateRerenderRequestedisfalse, so theelsebranch (line 4486–4489) clears the timeout and stops scheduling - Render loop is dead. No more frames are produced. The screen stays black.
Why switching tabs fixes it
Switching terminal tabs sends SIGWINCH, which triggers handleResize() → requestRender(), which wakes up the dead loop.
Code references
All line numbers refer to @opentui/core packages/core/src/renderer.ts v0.4.5:
requestRender()(lines 1491–1534): early-return at line 1516 whenthis.renderingis true, only setsthis.immediateRerenderRequested = trueloop()scheduling decision (lines 4478–4489): readsimmediateRerenderRequestedat line 4478, clears it at line 4481 — but a request arriving between 4481 and thefinallyis lostloop()finallyblock (lines 4515–4521): setsthis.rendering = falsebut does NOT re-checkimmediateRerenderRequestedactivateFrame()(lines 1536–1548): clearsupdateScheduled = falsein itsfinally— another window whererequestRender()is silently dropped (guarded by!this.updateScheduledat line 1521)
Proposed fix
Based on upstream PR anomalyco/opentui#1086 (which was closed without merging — re-submission needed):
In loop()'s finally block, re-check immediateRerenderRequested and re-issue via setImmediate:
} finally {
this.rendering = false
if (this._destroyPending) {
this.finalizeDestroy()
} else if (this.immediateRerenderRequested) {
this.immediateRerenderRequested = false
setImmediate(() => {
if (!this._isDestroyed) this.requestRender()
})
}
this.resolveIdleIfNeeded()
}setImmediate (not process.nextTick) is needed because activateFrame() keeps updateScheduled = true until its own finally after loop() returns. process.nextTick fires before microtasks resume — before activateFrame's finally — so requestRender() would be blocked by the !updateScheduled guard at line 1521.
Upstream issue
This is a re-report of the upstream opentui issue:
- opentui issue tweak: keep completion options open when trigger is still present #789: "Render loop silently stalls when rendering flag blocks requestRender during event burst" — still open
- opentui PR type 'reasoning' was provided without its required following item #1086: Proposed fix — closed without merging
The fix needs to be applied in @opentui/core, released, and then pulled into the opencode build. Currently opencode v1.18.3 bundles @opentui/core v0.4.5 which has the bug.
Workarounds
opencode spawn— runs backend in a separate process from TUI, may reduce the event burst pressure on the render loopCtrl+P→ "Toggle console" — triggers a re-render via the console overlay toggle- Resize the terminal window — sends SIGWINCH, wakes the dead loop
Additional note
There is a related opentui issue anomalyco/opentui#1147 ("TextNodeRenderable.requestRender() silently drops renders when transiently detached") with an open fix PR anomalyco/opentui#1173. This could be a compounding factor, causing text content to freeze even when the render loop is technically alive. However, the primary symptom (entire screen black, not just text) matches the render loop stall described here.
Activity
github-actions commentedon Jul 19, 2026
This issue might be a duplicate of existing issues. Please check:
If your issue is specifically triggered during agent work (not at startup), it may be a distinct race condition as you've described. But linking these for cross-reference may help track down a shared root cause.
AH64-dll commentedon Jul 19, 2026
Thanks for the cross-references. I checked all three:
TUI hangs on black screen at boot (OpenTUI) since v1.17.0 — Ubuntu/Wayland, all terminals, no plugins #32361 (black at boot, Wayland) and Possible regression in v1.17.10 on WSL: TUI shows black screen and no prompt can be entered #33887 (WSL regression at v1.17.10): These are startup hangs — the terminal-capability DCS query phase deadlocks before any render loop runs. Different root cause from the render-loop-stall described here.
TUI freezes on Debian 13 x86_64 / XFCE / X11 — blank screen, only kill -9 works #35494 (Debian 13, renders zero output): Most likely related — the reporter notes "Render loop timer fires every 1s but produces zero output." This could share the same
requestRender()→loop()→finallyrace documented here, triggered during the initial render burst at startup rather than during agent work. The workaround of switching terminal tabs (SIGWINCH →handleResize→requestRender()) might also work for that case.All four issues trace back to the same fragile render-loop scheduling in
@opentui/coreCliRenderer— the gap between flag checks and thefinallyblock where re-render requests are silently dropped.AH64-dll commentedon Jul 19, 2026
Fix PRs are up:
tool_useids must be unique #1279 (the fix itself): fix(renderer): prevent render loop stall by re-checking immediateRerenderRequested in loop() finally block opentui#1279To unblock yourself in the meantime, you can:
or apply the one-line fix manually to
node_modules/@opentui/core/renderer.js:In the
loop()function, find thefinallyblock (around line 4500) and change:to:
github-actions commentedon Sep 19, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.