Skip to content

v1.4.3: runaway memory growth followed by recursive Zig panic and deliberate ud2 trap in renderer/title path #22084

Closed as not planned

Description

@diegonix

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:

opencode hit 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 deliberate ud2 trap.


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: ud2

This 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 = SIGSEGV
  • 0x4 = SIGILL
  • 0x7 = SIGBUS
  • 0x6 = SIGABRT
  • 0x8 = SIGFPE
  • 0x1 = SIGHUP
  • 0xf = 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:

ud2

which 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:

  1. Renderer / terminal title / output path

    • renderer.CliRenderer.setTerminalTitle
    • renderer.CliRenderer.writeOut
    • Zig fixed buffer stream writes
  2. Text buffer / virtual chunk / grapheme growth

    • text-buffer-view.VirtualChunk
    • grapheme pool / grapheme tracker
    • growCapacity
    • ensureTotalCapacity
  3. 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_282206b4bffeQYRb0sRvfqnlyG

The 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 coredumpctl output
  • GDB disassembly showing the ud2
  • preserved Zig .so module 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:

  1. A long-running session accumulates pathological state or leaks memory.

  2. Memory grows until global OOM.

  3. During or after this degradation, opencode enters a Zig panic / error-reporting path.

  4. That path appears to involve:

    • terminal output writes
    • fixed buffer stream writes
    • renderer.CliRenderer.setTerminalTitle
  5. The panic path itself fails or re-enters panic, indicated by:

    • OutOfMemory
    • aborting due to recursive panic
  6. Final termination occurs via an explicit ud2 trap.

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

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    perfIndicates a performance issue or need for optimization
    on Apr 11, 2026
  2. github-actions commented on Apr 11, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of or related to existing issues. Please check:

    Note: your specific crash path (Zig renderer ud2 trap via renderer.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.

  3. diegonix commented on Apr 18, 2026

    @diegonix
    Author

    I’m still seeing what looks like the same crash signature after the original report.
    New occurrences:

    • 2026-04-17
    • 2026-04-18

    What matches the original issue:

    • same final signal: SIGILL
    • same top crash frame: opencode + 0x2e5e0c4
    • latest coredump still disassembles the faulting RIP as ud2
    • latest stderr/screenshot again shows: panic: Floating point error at address 0x48B0A02
    • the same 0x48B0A02 address appears in the recent stack traces

    So 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:
    7336da5387ce01a05b890cdc77fe6287dde90d9c

    What 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.

  4. removed
    bugSomething isn't working
    perfIndicates a performance issue or need for optimization
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  5. github-actions commented on Jul 4, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions