Repository navigation
v1.4.3: runaway memory growth followed by recursive Zig panic and deliberate ud2 trap in renderer/title path #22084
Description
Description
Disclaimer: I do not speak English, so I asked an AI to help me structure this issue in English.
However, this report is based on a real crash investigation that I personally spent a significant amount of time debugging and collecting evidence for.
Please do not treat it as low-value or AI-generated noise: the evidence below is real and was manually collected from the crashing system.
TL;DR
- Memory usage grows until global OOM (~13.6GB RSS)
- After that, process enters Zig panic path
- Panic path fails → recursive panic
- Final crash is intentional (
ud2), not CPU issue - Stack points to renderer.CliRenderer.setTerminalTitle
- Possibly related to Memory Megathread #20695
Summary
opencode v1.4.3 entered a pathological memory growth state, triggered a global OOM, and then terminated with SIGILL.
The SIGILL was not caused by an unsupported CPU instruction. The crashing instruction is an explicit ud2 trap, which indicates an intentional fatal abort path.
I investigated the core dump and a loaded temporary shared module, and the strongest evidence points to a Zig-based renderer/runtime path involving:
renderer.CliRenderer.setTerminalTitle- Zig stdlib writer paths (
Io.Writer.writeAll,FixedBufferStream.write, etc.) OutOfMemory- recursive panic / panic-reporting failure
The strongest current hypothesis is:
opencodehit an edge case that caused runaway memory growth, likely in renderer/text-buffer/grapheme/virtual-chunk state.
Once memory pressure became severe, the Zig runtime entered a panic/error path while handling terminal output/title updates, that path itself failed, and the process ended in recursive panic followed by a deliberateud2trap.
Environment
- opencode version:
1.4.3 - binary path:
~/.opencode/bin/opencode - binary sha256:
28e3582637877da8953d37449d20fc5034de0a8d0284d225e0fc6d6b224cbcdd - OS: Ubuntu 24.04 x86_64
- command line at crash time:
opencode -s ses_282206b4bffeQYRb0sRvfqnlyG
Binary metadata:
~/.opencode/bin/opencode: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=7336da5387ce01a05b890cdc77fe6287dde90d9c, not stripped
Linked libs:
libc.so.6
libpthread.so.0
libdl.so.2
libm.so.6
Primary observed failure
1) Global OOM occurred first
Kernel log:
Out of memory: Killed process 4028065 (MainThread) total-vm:46245028kB, anon-rss:14235716kB, file-rss:804kB, shmem-rss:0kB, UID:1001 pgtables:43472kB oom_score_adj:200
oom_reaper: reaped process 4028065 (MainThread), now anon-rss:112kB, file-rss:804kB, shmem-rss:0kB
Relevant context:
oom-kill:constraint=CONSTRAINT_NONE,... global_oom, task=MainThread
This indicates:
- ~44 GB virtual memory
- ~13.6 GB anonymous RSS
- global OOM, not just a small local allocation failure
This strongly suggests runaway allocation / memory leak / unbounded retained state.
2) opencode then produced a core dump with SIGILL
coredumpctl debug showed:
Signal: 4 (ILL)
Program terminated with signal SIGILL, Illegal instruction.
However, disassembly at RIP shows this was not an accidental invalid instruction:
=> 0x000000000305e0c4: ud2
0x000000000305e0c6: int3
0x000000000305e0c7: int3
...So the crash path is an intentional trap / fatal abort path.
Important evidence
A. The main binary explicitly traps with ud2
Disassembly around RIP:
0x000000000305e0b2: call 0x2af81d0
0x000000000305e0b7: mov $0xf,%edi
0x000000000305e0bc: mov %rbx,%rsi
0x000000000305e0bf: call 0x2af81d0
=> 0x000000000305e0c4: ud2This is not a CPU-feature mismatch. It is a deliberate crash path.
B. The helper called before ud2 appears to be checking signal handlers
Disassembly of 0x2af81d0:
2af81d0: push %rbp
2af81d1: mov %rsp,%rbp
2af81d4: movzbl %dil,%edi
2af81d8: xor %edx,%edx
2af81da: call sigaction@plt
2af81df: cmp $0xffffffff,%eax
...Before trapping, the code calls this helper repeatedly with signal numbers:
0xb= SIGSEGV0x4= SIGILL0x7= SIGBUS0x6= SIGABRT0x8= SIGFPE0x1= SIGHUP0xf= SIGTERM
This further supports that the ud2 is part of an intentional fatal runtime path.
C. A temporary Zig shared object was active on the crashing path
Loaded module:
/tmp/.fea5f4ff7f773bf9-00000000.so
I preserved a copy and inspected it. It is:
ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), static-pie linked, with debug_info, not stripped
This module contains debug info and symbols.
D. Resolved frame-to-line mappings from the temporary module
Using addr2line on the frame offsets from the core:
+0xc9efa
compiler_rt.memcpy.copyFixedLength
/Users/runner/hostedtoolcache/zig/0.15.2/arm64/lib/compiler_rt/memcpy.zig:170
+0x8b11d
Io.fixed_buffer_stream.FixedBufferStream([]u8).write
/Users/runner/hostedtoolcache/zig/0.15.2/arm64/lib/std/Io/fixed_buffer_stream.zig:51
+0x880d2
Io.DeprecatedWriter.write
/Users/runner/hostedtoolcache/zig/0.15.2/arm64/lib/std/Io/DeprecatedWriter.zig:13
+0x75d8c
Io.Writer.writeAll
/Users/runner/hostedtoolcache/zig/0.15.2/arm64/lib/std/Io/Writer.zig:532
+0x8b4cd
renderer.CliRenderer.setTerminalTitle
/Users/runner/work/opentui/opentui/packages/core/src/zig/renderer.zig:1260
This is the most concrete evidence I have.
The resolved chain clearly goes through:
- Zig stdlib writer paths
- memcpy
- terminal title update path
E. Strings inside the Zig module strongly indicate OOM + recursive panic + renderer/text-buffer state
Relevant extracted strings include:
aborting due to recursive panic
panic: _buffer_
OutOfMemory
InvalidWtf8
debug.defaultPanic
debug.panicking
debug.printLastUnwindError
debug.printUnwindError
waitForOtherThreadToFinishPanicking
error{OutOfMemory}![]text-buffer-view.VirtualChunk
Also present:
renderer.CliRenderer.writeOut
setTerminalTitle
text-buffer-view.VirtualChunk
array_list.Aligned(text-buffer-view.VirtualChunk,null).growCapacity
array_list.Aligned(text-buffer-view.VirtualChunk,null).ensureTotalCapacity
and:
Fatal: no grapheme bytes in pool for gid :
LinkTracker.addCellRef getOrPut failed: GraphemeTracker.add failed: GraphemeTracker.add incref failed:
GraphemePool.alloc FAILED for grapheme (len=
This strongly suggests that the renderer / text-buffer / grapheme path may be involved in:
- the pathological memory growth,
- the failing error path,
- or both.
Why this does not look like a CPU instruction compatibility issue
Although the final signal is SIGILL, this is not because of AVX/SSE/feature mismatch.
The exact crashing instruction is:
ud2which is an intentional invalid opcode used for fatal aborts.
So this should be treated as:
- a deliberate crash path taken by the program/runtime
- not a hardware instruction support issue
Potential areas to inspect
Based on the current evidence, the most promising subsystems to audit are:
-
Renderer / terminal title / output path
renderer.CliRenderer.setTerminalTitlerenderer.CliRenderer.writeOut- Zig fixed buffer stream writes
-
Text buffer / virtual chunk / grapheme growth
text-buffer-view.VirtualChunk- grapheme pool / grapheme tracker
growCapacityensureTotalCapacity
-
Panic/error-reporting path under low-memory conditions
- recursive panic evidence suggests the error path may not degrade safely under memory exhaustion
Reproduction status
Not yet minimized into a deterministic small reproducer.
Observed invocation:
opencode -s ses_282206b4bffeQYRb0sRvfqnlyGThe failure happened during a real long-running session and not a synthetic micro-test.
Given the evidence above, reproduction may require:
- long-lived session mode
- substantial renderer / terminal output activity
- repeated title/status updates
- or accumulation of large text-buffer / grapheme state
Evidence available on request
I can provide:
- full kernel OOM logs
- full
coredumpctloutput - GDB disassembly showing the
ud2 - preserved Zig
.somodule with debug info - exact resolved frame-to-line mappings shown above
Plugins
No response
OpenCode version
1.4.3
Steps to reproduce
Working hypothesis
Current best hypothesis is:
-
A long-running session accumulates pathological state or leaks memory.
-
Memory grows until global OOM.
-
During or after this degradation,
opencodeenters a Zig panic / error-reporting path. -
That path appears to involve:
- terminal output writes
- fixed buffer stream writes
renderer.CliRenderer.setTerminalTitle
-
The panic path itself fails or re-enters panic, indicated by:
OutOfMemoryaborting due to recursive panic
-
Final termination occurs via an explicit
ud2trap.
So the issue may be a combination of:
- root cause: runaway memory growth / retained renderer or text-buffer state
- secondary bug: panic/error-reporting path is not robust under memory pressure and recursively panics
Screenshot and/or share link
No response
Operating System
Ubuntu 24.04
Terminal
Ghostty
Activity
github-actions commentedon Apr 11, 2026
This issue might be a duplicate of or related to existing issues. Please check:
Note: your specific crash path (Zig renderer
ud2trap viarenderer.CliRenderer.setTerminalTitle+ grapheme pool OOM + recursive panic) appears distinct from the JS/Bun heap leaks tracked in those issues, so this may warrant its own investigation — just flagging for visibility.diegonix commentedon Apr 18, 2026
I’m still seeing what looks like the same crash signature after the original report.
New occurrences:
What matches the original issue:
SIGILLopencode + 0x2e5e0c4ud2panic: Floating point error at address 0x48B0A020x48B0A02address appears in the recent stack tracesSo this does not look like a new unrelated Linux/CPU issue. It looks like the same fatal runtime path is still being hit on
opencode 1.4.11.One notable detail: the current binary still reports the same ELF BuildID noted in the original report:
7336da5387ce01a05b890cdc77fe6287dde90d9cWhat I have not re-proven yet for the newest crashes is the full OOM/title-renderer chain from the original investigation. But the crash signature itself is clearly recurring.
github-actions commentedon Jul 4, 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.