[GH-ISSUE #705] getPallete() can block until timeout #960

Open
opened 2026-03-14 09:09:14 +03:00 by kerem · 3 comments
Owner

Originally created by @akronb on GitHub (Feb 18, 2026).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/705

Summary

getPalette() currently behaves as an all-or-timeout operation.
In environments with partial OSC support, it can frequently resolve only after the full timeout (default 5000ms), which makes downstream apps feel frozen during startup when they wait for palette detection.

Why this is a problem

For apps using terminal colors for initial theming, startup gets coupled to terminal response timing.
If OSC responses are partial/incomplete, the app waits for timeout instead of getting early usable data (e.g. first 16 colors) and continuing.

Originally created by @akronb on GitHub (Feb 18, 2026). Original GitHub issue: https://github.com/anomalyco/opentui/issues/705 ### Summary `getPalette()` currently behaves as an all-or-timeout operation. In environments with partial OSC support, it can frequently resolve only after the full timeout (default 5000ms), which makes downstream apps feel frozen during startup when they wait for palette detection. ### Why this is a problem For apps using terminal colors for initial theming, startup gets coupled to terminal response timing. If OSC responses are partial/incomplete, the app waits for timeout instead of getting early usable data (e.g. first 16 colors) and continuing.
Author
Owner

@simonklee commented on GitHub (Feb 21, 2026):

I tried looking at it but not happy where it landed.

<!-- gh-comment-id:3938806258 --> @simonklee commented on GitHub (Feb 21, 2026): I tried looking at it but not happy where it landed.
Author
Owner

@MaxDillon commented on GitHub (Mar 2, 2026):

https://github.com/anomalyco/opencode/issues/7979#issuecomment-3981502176

stty -echo -icanon min 0 time 5
printf '\033]10;?\007\033]11;?\007\033]12;?\007\033]13;?\007\033]14;?\007\033]15;?\007\033]16;?\007\033]17;?\007\033]19;?\007'
dd bs=1 count=512 2>/dev/null | /bin/cat -v stty sane

The root issue is in the interaction between the getPallette function in opentui and ghostty. Here are the different responses to this command in ghostty and wezterm:

ghostty:

^[]10;rgb:cdcd/d6d6/f4f4^G^[]11;rgb:1e1e/1e1e/2e2e^G^[]12;rgb:f5f5/e0e0/dcdc^G%

wezterm:

^[]10;rgb:cdcd/d6d6/f4f4^[\^[]11;rgb:1e1e/1e1e/2e2e^[\^[]12;rgb:f5f5/e0e0/dcdc^[\^[]17;rgb:5858/5b5b/7070^[\^[]19;rgb:cdcd/d6d6/f4f4^[\% 

We see that ghostty gets back 10, 11, 12 (foreground, background, cursor), but does not return the 17/19. However neither of them return 13, 14, 15, 16, all of which are expected from special colors to return cleanly:

class TerminalPallette implements TerminalPalletteDetector {
...
private async querySpecialColors(timeoutMs = 1200): Promise<Record<number, Hex>> {
const out = this.stdout
const inp = this.stdin
const results: Record<number, Hex> = {
10: null,
11: null,
12: null,
13: null,
14: null,
15: null,
16: null,
17: null,
19: null,
}
...

This should lead to both wezterm and ghostty hanging, but there is a backup idleTimer that times out after 150ms of inactivity (no escape sequences). Ghostty has a really chatty protocol, although I haven't looked too closely into why:

I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[

It sends a continuous stream of these escape sequences, preventing the inactivity timeout from triggering.

The fix is in opetui, changing the timeout logic to only reset if the results table has been updated in the allotted time. It does not reset on unrelated escape sequences.

Here is a specific instance of this happening in opencode-ghostty. The fix I suggested is to add a check before resetting the idle timer to see if the data fetched was useful e.g. if there was a new OSC_SPECIAL_RESPONSE/OSC4_RESPONSE regex match.


const OSC4_RESPONSE =
  /\x1b]4;(\d+);(?:(?:rgb:)([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)|#([0-9a-fA-F]{6}))(?:\x07|\x1b\\)/g

const OSC_SPECIAL_RESPONSE =
  /\x1b](\d+);(?:(?:rgb:)([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)|#([0-9a-fA-F]{6}))(?:\x07|\x1b\\)/g
<!-- gh-comment-id:3981523348 --> @MaxDillon commented on GitHub (Mar 2, 2026): https://github.com/anomalyco/opencode/issues/7979#issuecomment-3981502176 > stty -echo -icanon min 0 time 5 > printf '\033]10;?\007\033]11;?\007\033]12;?\007\033]13;?\007\033]14;?\007\033]15;?\007\033]16;?\007\033]17;?\007\033]19;?\007' > dd bs=1 count=512 2>/dev/null | /bin/cat -v stty sane > > The root issue is in the interaction between the `getPallette` function in opentui and ghostty. Here are the different responses to this command in ghostty and wezterm: > > ghostty: > > ``` > ^[]10;rgb:cdcd/d6d6/f4f4^G^[]11;rgb:1e1e/1e1e/2e2e^G^[]12;rgb:f5f5/e0e0/dcdc^G% > ``` > > wezterm: > > ``` > ^[]10;rgb:cdcd/d6d6/f4f4^[\^[]11;rgb:1e1e/1e1e/2e2e^[\^[]12;rgb:f5f5/e0e0/dcdc^[\^[]17;rgb:5858/5b5b/7070^[\^[]19;rgb:cdcd/d6d6/f4f4^[\% > ``` > > We see that ghostty gets back 10, 11, 12 (foreground, background, cursor), but does not return the 17/19. However neither of them return 13, 14, 15, 16, all of which are expected from special colors to return cleanly: > > class TerminalPallette implements TerminalPalletteDetector { > ... > private async querySpecialColors(timeoutMs = 1200): Promise<Record<number, Hex>> { > const out = this.stdout > const inp = this.stdin > const results: Record<number, Hex> = { > 10: null, > 11: null, > 12: null, > 13: null, > 14: null, > 15: null, > 16: null, > 17: null, > 19: null, > } > ... > > This should lead to both wezterm and ghostty hanging, but there is a backup idleTimer that times out after 150ms of inactivity (no escape sequences). Ghostty has a really chatty protocol, although I haven't looked too closely into why: > > ``` > I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[I\x1B[ > ``` > > It sends a continuous stream of these escape sequences, preventing the inactivity timeout from triggering. > > The fix is in opetui, changing the timeout logic to only reset if the results table has been updated in the allotted time. It does not reset on unrelated escape sequences. Here is a specific instance of this happening in opencode-ghostty. The fix I suggested is to add a check before resetting the idle timer to see if the data fetched was useful e.g. if there was a new OSC_SPECIAL_RESPONSE/OSC4_RESPONSE regex match. ```js const OSC4_RESPONSE = /\x1b]4;(\d+);(?:(?:rgb:)([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)|#([0-9a-fA-F]{6}))(?:\x07|\x1b\\)/g const OSC_SPECIAL_RESPONSE = /\x1b](\d+);(?:(?:rgb:)([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)|#([0-9a-fA-F]{6}))(?:\x07|\x1b\\)/g ```
Author
Owner

@akronb commented on GitHub (Mar 4, 2026):

@MaxDillon thanks a lot, that really helped!

@simonklee I’m not sure if I can close that issue, so I’ll leave it to you

<!-- gh-comment-id:4000201603 --> @akronb commented on GitHub (Mar 4, 2026): @MaxDillon thanks a lot, that really helped! @simonklee I’m not sure if I can close that issue, so I’ll leave it to you
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#960
No description provided.