Skip to content

Commit cda4bf6

Browse files
authored
fix: sidestep the node:wasi fast-call crash instead of requiring Node 24 (#9)
Bisected: 22.21.0 ok, 22.21.1 crash (all platforms, not Linux-only — the WasiFunction fast-call signature backport, nodejs/node#59600). --no-turbo-fast-api-calls on the worker avoids the broken fast path; the platform guard is gone and any Node >= 20 works.
1 parent 288a896 commit cda4bf6

5 files changed

Lines changed: 37 additions & 28 deletions

File tree

‎.changeset/wasi-fast-calls.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
default: patch
3+
---
4+
5+
#### The wasm engine no longer crashes on Node 22.21.1+
6+
7+
Node 22.21.1 backported a `node:wasi` fast-call change
8+
([nodejs/node#59600](https://github.com/nodejs/node/pull/59600)) that
9+
makes wasm engines segfault during startup, on every platform. The
10+
engine worker now always runs with `--no-turbo-fast-api-calls`, which
11+
sidesteps the broken fast path — any Node ≥ 20 works again, and the
12+
briefly-introduced "Node 24 required on Linux" refusal is gone.

‎.github/workflows/trail-engine.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ jobs:
2727

2828
- uses: actions/setup-node@v4
2929
with:
30-
# 24+: node:wasi segfaults on x86_64 Linux before Node 24
31-
# (nodejs/node#53087) — the backend itself refuses there too.
30+
# Current LTS. (The 22.21.1+ node:wasi fast-call segfault is
31+
# worked around in the backend itself — see wasm-backend.ts.)
3232
node-version: 24
3333

3434
- name: Re-pin to the newest stable release

‎CLAUDE.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ story and `LICENSING.md` for why this repo is MIT around a GPL engine.
3737

3838
## Gotchas that cost real time
3939

40-
- **Node ≥ 24 on Linux for the wasm backend** — `node:wasi` corrupts
41-
memory on x86_64 Linux in older lines (nodejs/node#53087); the backend
42-
refuses there with a pointer. Workflows that run the wasm engine must
43-
use Node 24.
40+
- **`node:wasi` fast calls corrupt memory from Node 22.21.1** (the
41+
WasiFunction fast-call signature backport, nodejs/node#59600): the
42+
engine segfaults during Guile startup, all platforms, with empty
43+
stderr. The worker always runs with `--no-turbo-fast-api-calls`, which
44+
sidesteps it — never remove that flag without re-bisecting. (Node 24
45+
is unaffected either way.)
4446
- **PRs opened with `GITHUB_TOKEN` (e.g. by trail-engine.yml) don't
4547
trigger workflows**, so required checks never run on them. Nudge with
4648
an empty commit pushed by a real user, or set a `GH_PAT` secret (the

‎README.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ MCP server that engraves [GNU LilyPond](https://lilypond.org) sources into
66
placeable assets: cropped PDF, EPS, SVG, and PNG, sized to the music rather
77
than a full page — ready to drop into page-layout software such as InDesign.
88

9-
Nothing to install beyond Node (24 or newer): without a local LilyPond,
10-
the server fetches a WebAssembly build of the engine and engraves with
11-
that.
9+
Nothing to install beyond Node: without a local LilyPond, the server
10+
fetches a WebAssembly build of the engine and engraves with that.
1211

1312
## Quick start
1413

@@ -53,13 +52,14 @@ the source and retry.
5352

5453
| | native | wasm |
5554
|---|---|---|
56-
| needs | `lilypond` (≥ 2.26) and `gs` on `PATH` | Node ≥ 24 |
55+
| needs | `lilypond` (≥ 2.26) and `gs` on `PATH` | nothing |
5756
| formats | pdf, eps, svg, png | pdf, eps, svg, png (engine ≥ p0.1.3; earlier engines: svg, eps) |
5857
| engine | whatever is installed | [lilypond-wasi](https://github.com/wspringer/lilypond-wasi) release, pinned in `engine.json` |
5958

60-
(Node ≥ 24 for the wasm backend because `node:wasi` corrupts memory on
61-
x86_64 Linux in older lines — [nodejs/node#53087](https://github.com/nodejs/node/issues/53087);
62-
the server detects this and says so rather than crashing.)
59+
(Node 22.21.1 through 23.x ship a `node:wasi` fast-call regression that
60+
segfaults wasm engines — [nodejs/node#59600](https://github.com/nodejs/node/pull/59600);
61+
the server sidesteps it automatically by running the engine with
62+
`--no-turbo-fast-api-calls`, so any Node ≥ 20 works.)
6363

6464
The server picks automatically: native when an installed LilyPond responds,
6565
wasm otherwise. Force one with `LILYPOND_MCP_BACKEND=native|wasm`.

‎src/engine/wasm-backend.ts‎

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,16 @@ function runWorker(
5252
if (!workerPath) {
5353
throw new Error(`engine worker not built — run npm run build (looked at: ${candidates.join(", ")})`);
5454
}
55-
const execArgv = opts.withExnrefFlag ? ["--experimental-wasm-exnref"] : [];
55+
// --no-turbo-fast-api-calls, always: Node's WASI syscall "fast calls"
56+
// corrupt memory from 22.21.1 (the WasiFunction fast-call signature
57+
// backport, nodejs/node#59600) — the engine segfaults during Guile
58+
// startup, on every platform. Disabling the fast path fixes it; the
59+
// cost is noise next to an engrave, and it is harmless where the bug
60+
// is absent. Bisected 2026-08-25: 22.21.0 ok, 22.21.1 crash, 24 ok.
61+
const execArgv = [
62+
"--no-turbo-fast-api-calls",
63+
...(opts.withExnrefFlag ? ["--experimental-wasm-exnref"] : []),
64+
];
5665
return new Promise((resolve, reject) => {
5766
const child = spawn(
5867
process.execPath,
@@ -109,20 +118,6 @@ async function runEngine(job: object): Promise<WorkerReply> {
109118
}
110119

111120
export async function engraveWasm(req: EngraveRequest, pin: EnginePin): Promise<EngraveResult> {
112-
// Node's WASI syscall fast path corrupts memory on x86_64 Linux before
113-
// Node 24 (nodejs/node#53087, shipped in 22.2.0, closed unfixed) — the
114-
// engine segfaults during Guile startup. Refuse with a pointer instead.
115-
const nodeMajor = Number(process.versions.node.split(".")[0]);
116-
if (process.platform === "linux" && nodeMajor < 24) {
117-
return {
118-
ok: false,
119-
outputs: {},
120-
errors:
121-
`the wasm engine needs Node >= 24 on Linux (you are on ${process.version}): ` +
122-
`node:wasi crashes there on older lines (nodejs/node#53087). ` +
123-
`Upgrade Node, or install LilyPond to use the native backend.`,
124-
};
125-
}
126121
const { dir, manifest } = await ensureEngine(pin);
127122

128123
const unsupported = req.formats.filter((f) => !manifest.formats.includes(f));

0 commit comments

Comments
 (0)