Skip to content

bug(core): bufferDrawBox panics (SIGTRAP brk 1) after #920 refactor — regression in 0.1.103 vs 0.1.92 #971

Closed

Description

@moraisdev

Summary

Between v0.1.92 and v0.1.103 bufferDrawBox grew from ~2.4 KB to ~14.2 KB and started panicking under load on macOS arm64 (SIGTRAP/brk 1) and rendering with corrupt colors on Linux WSL2 + Windows Terminal. The most likely culprit is the buffer.zig refactor in #920 (14480ef feat: add support for bottom title and alignment in BoxRenderable), which the PR description states was a deliberate rewrite of drawBox so title and bottomTitle share a draw path.

A downstream consumer report with the original .ips crash dump and a Linux render-corruption confirmation is here: automagik-si/genie#1390

Evidence

_bufferDrawBox size jumped 6× in 0.1.103

From nm -U on libopentui.dylib (darwin-arm64), computing the gap to the next exported symbol:

@opentui/core _bufferDrawBox start Approx function size
0.1.92 0x43e58 ~2,372 bytes
0.1.97 0x42274 ~2,372 bytes
0.1.99 0x41f54 larger
0.1.102 0x44b80 larger
0.1.103 0x46a90 ~14,164 bytes

macOS arm64 crash report

Exception:    EXC_BREAKPOINT (SIGTRAP) / brk 1
Faulting frame: libopentui.dylib`_bufferDrawBox + 2672
Image UUID:   8F6BC5C3-5E73-3736-BC94-9F889C9DF632  (0.1.103, darwin-arm64)
OS:           macOS 15.6.1, Mac16,1, arm64
Bun:          1.3.12
Terminal:     Warp v0.2026.04.15.08.stable_02
Process age:  ~18 minutes between launch and crash
Reproducibility: sporadic; survived 91 s + 9× SIGWINCH in our short-window repro

The crash dump JSON is attached on the downstream issue (gist 4c824b03fe87964ff1b6ef2b2f5d8ea5). Key detail: +2672 is outside the entire 0.1.92 function (which is only 2,372 bytes total), so this panic is in code that did not exist before the refactor.

Linux render corruption (no SIGTRAP, same regression)

A second consumer reports on Linux WSL2 + Windows Terminal + tmux + bun, after upgrading to 0.1.103:

  • Sidebar selection highlight renders salmon/coral where a #7c3aed violet was emitted
  • Header bar background renders salmon where a #241838 dark navy was emitted
  • Intermittent flicker, desaturated palette
  • Manually replacing 0.1.103 with 0.1.91 in node_modules and restarting the renderer fixes it cleanly, with no other config change

Full report: automagik-si/genie#1390 (comment) (linked back here).

New panic-class strings introduced in 0.1.103 (absent in 0.1.92)

comm -13 of the strings tables across the two dylibs:

Invalid buffer dimensions: Failed to create optimized buffer: Failed to send pending queries:
LinkTracker.addCellRef getOrPut failed: GraphemeTracker.add failed: GraphemeTracker.add incref failed:

Two new bookkeeping subsystems (GraphemeTracker, LinkTracker) appear to be invoked from inside bufferDrawBox (or its callees). Any allocation/getOrPut failure in those paths panics across the FFI boundary, which surfaces as EXC_BREAKPOINT on macOS and (presumably) silent state corruption on Linux when the panic is masked.

ABI break shipped as a patch bump

-bufferDrawBox(buffer, x, y, width, height, borderChars, packedOptions, borderColor, backgroundColor, title)
+bufferDrawBox(buffer, x, y, width, height, borderChars, packedOptions, borderColor, backgroundColor, title, bottomTitle)
-function packDrawOptions(border2, shouldFill, titleAlignment) { ... }
+function packDrawOptions(border2, shouldFill, titleAlignment, bottomTitleAlignment) { ... }

The JS wrapper handles the new parameter, so consumers who call drawBox(options) from JS aren't visibly broken at the API level — but the new code paths inside the native side panic on legitimate inputs that 0.1.92 accepted.

Reproduction (downstream)

We could not deterministically trigger the macOS panic in a 91-second window with periodic SIGWINCH; it requires longer runs and (we suspect) specific state transitions in the parent React reconciler. The downstream genie TUI reproduces it after several minutes of normal use.

If a maintainer wants to bisect, the suspected starting point is the buffer.zig drawBox refactor in #920. The Linux render corruption is the more accessible repro: install genie with @opentui/[email protected] on WSL2 + Windows Terminal and look at the sidebar.

Asks

  1. Treat 0.1.103 as a regression worth a follow-up patch, not just a doc note. The downstream ^0.1.91 semver convention means anyone who installs after 2026-04-23T22:15:08Z (when 0.1.103 hit npm) gets it automatically.
  2. Confirm whether feat: add support for bottom title and alignment in BoxRenderable #920 plus the surrounding GraphemeTracker/LinkTracker work is the source. If yes, would it make sense to:
    • Validate (x, y, width, height) at the public boundary of bufferDrawBox and return an error/no-op instead of @panic/unreachable? Panic across an FFI boundary is hard to recover from for embedders (Bun aborts).
    • Include the offending (x, y, w, h, buffer.w, buffer.h) in the panic message so the symbol + stderr can pinpoint the bad call site?
  3. If a 0.1.104 is feasible that gates the new bookkeeping behind a flag or fixes the bounds, that's the cleanest path. Otherwise downstream consumers are pinning to 0.1.91/0.1.92.

Happy to provide additional artifacts, run a custom debug build with extra instrumentation, or test a candidate fix on the macOS arm64 + Warp setup.

Activity

  1. kommander commented on Apr 29, 2026

    @kommander
    Collaborator

    @viralcodex could you maybe check that?

  2. viralcodex commented on Apr 29, 2026

    @viralcodex
    Contributor

    @viralcodex could you maybe check that?

    Yeah sure, will look into it later today after work

  3. viralcodex commented on Apr 30, 2026

    @viralcodex
    Contributor

    Hi @kommander @simonklee , did some finding, and from what I can find, there's a real Grapheme handling issue on composite characters (unicode, CJK, etc), this comes because of adding the title to the right side on clipping.

    The path i was able to find goes from computeBoxTitleLayout to drawText which allocates grapheme pool id before it knows that it can be stored. Inside setInternal if grapheme overflows, it takes if (x + width > self.width) branch and returns without freeing the ID allocation. Then in grapheme.zig, when stale packed grapheme ID is used after slot reuse, it panics with this error in terminal:
    GraphemeTracker.add incref failed: error.WrongGeneration...

    In my understanding, this happens with any right title being clipped & not being able to render on the terminal with graphemeID.

    I could be wrong, but this is what I was able to uncover.

    Image
  4. viralcodex commented on Apr 30, 2026

    @viralcodex
    Contributor

    ok so I did some more checking so mostly I could see that the issue boils down to GraphemePool where we try to add a stale id but panics due to older generation, this seems to happen randomly as the pool ids are exhausted which seems to corroborate with the OP's random crash timings.

    From my side, I can say either we stop throwing and handle it gracefully by removing that Id from the pool and then show a warning. Or we can send an error instead of panicking which cannot be handled by the Typescript which crashes the TUI process

  5. simonklee commented on Aug 24, 2026

    @simonklee
    Member

    Current native code prevents the reported clipped-grapheme allocation path
    before tracker ownership, and relevant native regressions pass.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions