anomalyco / anomalyco/opencode

tui: nested Mermaid states cause infinite allocation and OOM

Open
#48,573 0 comments 0 reactions 1 assignee View on GitHub

@simonklee is already working on this.

Since Sep 11, 2026.

2.0
Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Description

Rendering a nested Mermaid state diagram in the V2 TUI causes immediate, unbounded memory allocation. The reporter experienced an OOM and whole-machine crash. Expected: render the diagram or reject it without exhausting memory.

Confirmed cause: addSideParallelTransition() in packages/merman/src/state/routing.ts:790 assumes its vertical segment has nonzero length:

for (let y = startY + verticalStep; y !== endY; y += verticalStep)
  addCell(builder, { x: railX, y, char: "│" })

When startY === endY, verticalStep is 1: the loop starts past its endpoint and increments forever, appending both cells and path points. Vertical routing selects this side route for states in different composite parents, even when they share a row.

The original diagram reaches this through automatic LR → TB reflow at layoutMaxWidth: 120; its failing route has startY = endY = 13. The canvas-size check runs after route construction, and the route-search budget does not cover this loop.

Reproduced deterministically from checkout 770420bd using the actual parser/layout/drawing code bundled for Node 24.11.1. Isolated subprocesses used a 96 MiB old-space cap, 4 MiB semi-space cap, CPU/wall-time limits, and disabled core dumps:

Input Current code Experimental bounded-loop correction
Original diagram OOM in 0.45 s; peak RSS 163 MiB Completes in 0.71 s; peak RSS 102 MiB
Reduced fixture below OOM in 0.24 s; peak RSS 164 MiB Completes in 0.13 s; peak RSS 71 MiB

Error: Allocation failed - JavaScript heap out of memory.

The experimental correction used a directional inequality to avoid walking a zero-length segment. It confirms the allocation cause; production route geometry and regression coverage still need fixing. Suggested regression seam: drawStateDiagramGrid, including automatic reflow.

Plugins

Reported diagnostic snapshot: boxedcode-pro 0.16.1, opencode-gemini, opencode-trajectory, opencode-ultra, and one project-local plugin (private name/path omitted). Plugin activation was not independently rechecked. The isolated reproduction directly invokes Merman and does not load these plugins.

OpenCode version

opencode2 v0.0.0-beta-19425 (verified with opencode2 --version; beta channel). The separate opencode executable reports 1.18.30.

Steps to reproduce
  1. Use a memory-limited isolated subprocess rather than the live TUI: the current code allocates indefinitely.
  2. Parse this source with parseMermaidStateDiagram from packages/merman/src/state/parser.ts:
stateDiagram-v2
direction TB
A --> B
A --> C
state D {
  state E {
    B --> F
  }
  B --> C
}
  1. Pass the result to drawStateDiagramGrid(parsed, { layoutMaxWidth: 120 }) from packages/merman/src/state/drawing.ts.
  2. Observe OOM while constructing the B → C side route (startY = endY = 8). The TUI reaches this same drawing entrypoint when rendering a mermaid fence.

Workaround: use a text fence to bypass Mermaid rendering.

Screenshot and/or share link

None collected; diagnosis used isolated renderer subprocesses.

Operating System

Darwin 25.6.0 arm64 (verified with uname -srm).

Terminal

Ghostty; reported snapshot: TERM=xterm-ghostty, COLORTERM=truecolor. Shell: /bin/zsh. Terminal and shell values are from the supplied snapshot, not independently rechecked.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.