You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
WASM memory corruption after freeing terminal that processed multi-codepoint grapheme clusters #141
Calling terminal.free() after writing any multi-codepoint grapheme cluster (flag emoji, skin tone modifiers, ZWJ sequences, keycap sequences) corrupts the shared WASM memory. All subsequent createTerminal() + write() calls crash with an out-of-bounds memory access.
Single-codepoint characters (ASCII, CJK, simple emoji like 😀) do not trigger the bug.
RuntimeError: Out of bounds memory access (evaluating 'this.exports.ghostty_terminal_write(this.handle, g, B.length)')
at write (ghostty-web.js:145:64)
The crash is in ghostty_terminal_write — the WASM linear memory has been corrupted by the preceding free(), so the next write() accesses invalid memory.
Impact
This blocks using ghostty-web for multi-terminal workflows (test suites, terminal pools, tab management) where terminals are created, used, and freed in sequence. If any terminal processes grapheme clusters before being freed, all subsequent terminals in the same WASM instance are broken.
Workaround
Don't free terminals that have processed multi-codepoint grapheme clusters. Leak them instead:
// Instead of term.free(), just abandon the reference// term.free() // ← skip thisterm=null;
Or use one process per terminal (expensive but avoids shared WASM state).
Some data on this one, from chasing a similar-looking fault in an embedder.
This bug is distinct from the cursorDownScroll one, and bec9e16 (#180) does not fix it. That distinction cost me a round of measurement, so it seems worth recording here.
The two present almost identically — memory access out of bounds originating in BitmapAllocator — but they have different triggers:
The fix: clear scrolled row cells in wasm patch #180 bug needs only a write: Screen.cursorDownScroll skipped clearing a scrolled-in row unless a background colour was set, leaving stale content_tag = .codepoint_grapheme markers behind that point at grapheme offsets which no longer exist.
This issue additionally requires free() — the multi-codepoint cluster write and the terminal teardown together. Applying the fix: clear scrolled row cells in wasm patch #180 hunk alone leaves it reproducing.
The faulting frame in both cases is BitmapAllocator.free (src/terminal/bitmap_allocator.zig). I confirmed that identification rather than guessing it: the shipped wasm-function[162] is byte-identical (177 bytes) to the same function in a from-source rebuild that reproduces the published 0.4.0 artifact bit-for-bit (d6f0326f1874ad2c).
One practical note for anyone reproducing this: a single fault poisons the shared WASM heap, so every case after the first in the same page reports a fault regardless of its own input. Test cases have to run on a fresh page (or at minimum a fresh module instance) or the results are meaningless — several of my early runs were invalid for exactly this reason before I caught it.
Environment: ghostty-web 0.4.0 from npm, Chromium 152 and Electron 42, Zig 0.15.2 for the rebuilds.
Activity
fix: zero-initialize WASM page buffers to prevent memory corruption (…
fix: zero-initialize WASM page buffers to prevent memory corruption (…
feat: grid host pool + visibility state machine + PTY host/session sp…
fix(ghostty): make GhosttyTerminal.free() idempotent
docs: mature-core candidates — xterm headless, alacritty_terminal, li…
tsukasagenesis commentedon Sep 15, 2026
Some data on this one, from chasing a similar-looking fault in an embedder.
This bug is distinct from the
cursorDownScrollone, and bec9e16 (#180) does not fix it. That distinction cost me a round of measurement, so it seems worth recording here.The two present almost identically —
memory access out of boundsoriginating inBitmapAllocator— but they have different triggers:Screen.cursorDownScrollskipped clearing a scrolled-in row unless a background colour was set, leaving stalecontent_tag = .codepoint_graphememarkers behind that point at grapheme offsets which no longer exist.free()— the multi-codepoint cluster write and the terminal teardown together. Applying the fix: clear scrolled row cells in wasm patch #180 hunk alone leaves it reproducing.The faulting frame in both cases is
BitmapAllocator.free(src/terminal/bitmap_allocator.zig). I confirmed that identification rather than guessing it: the shippedwasm-function[162]is byte-identical (177 bytes) to the same function in a from-source rebuild that reproduces the published 0.4.0 artifact bit-for-bit (d6f0326f1874ad2c).One practical note for anyone reproducing this: a single fault poisons the shared WASM heap, so every case after the first in the same page reports a fault regardless of its own input. Test cases have to run on a fresh page (or at minimum a fresh module instance) or the results are meaningless — several of my early runs were invalid for exactly this reason before I caught it.
Environment: ghostty-web 0.4.0 from npm, Chromium 152 and Electron 42, Zig 0.15.2 for the rebuilds.