fix(code-knowledge): keep tree-sitter grammars off V8's optimizing Wasm tier on Node 24 (#860) (#861)

On Node 24, V8's Turboshaft Wasm compiler exhausts its Zone memory while
tiering up tree-sitter grammar code and aborts the process with "Fatal
process out of memory: Zone" (nodejs/node#63421). The Swift grammar added
in #842 hits it right after its first parse, so `teamai codebase --extract`
on a repo with .swift files died with exit 133 and no fallback.

Set --wasm-tier-up-filter to an index no grammar has before the WASM
runtime starts, on Node 24 and later only. --liftoff-only has the same
effect but Node 24 ignores it when set at runtime. Node 20 and 22 keep
tier-up, which parses about 1.6x faster there.

Add Node 24 to the CI matrix so the regression test runs where it can fail.

Closes #860
Refs #842
This commit is contained in:
Saul Moro
2026-09-28 10:46:07 +08:00
committed by GitHub
parent e9368701a7
commit 5fb316c7b8
4 changed files with 39 additions and 1 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
node-version: [20, 22]
node-version: [20, 22, 24]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
+1
View File
@@ -33,6 +33,7 @@ All notable changes to this project will be documented in this file. See [standa
### 🐛 Bug Fixes
- On Node 24, `teamai codebase --extract`, and the `teamai import` and CI extract paths that run it, no longer abort with `Fatal process out of memory: Zone` on a repository with `.swift` files. V8's optimizing Wasm compiler (nodejs/node#63421) ran out of memory on the tree-sitter grammars, so the AST track now keeps them on V8's baseline tier on Node 24 and later. That also cuts the TypeScript grammar's peak memory there from about 1.4 GB to about 0.1 GB, for a parse about 1.6x slower; Node 20 and 22 are unchanged (for [#860](https://github.com/Tencent/teamai-cli/issues/860)).
- When `TEAMAI_GITLAB_HOST` and `GITLAB_URL` name different hosts, GitLab commands stop with an error naming both, before any request. A repo on `TEAMAI_GITLAB_HOST` was detected as GitLab while every API call, the token included, went to `GITLAB_URL`. An invalid `GITLAB_URL` is now reported as such by `init` instead of as a failed GitLab login (for [#789](https://github.com/Tencent/teamai-cli/issues/789)).
- A misspelled top-level key in `mcp/mcp.yaml` or `hooks/hooks.yaml` (`server:` for `servers:`, `hook:` for `hooks:`) no longer removes every installed team MCP server or hook: such a file read as empty. It now fails like a file that does not parse, so pull keeps what is installed, and pull and `teamai doctor` name the file, the keys found and the key expected. An extra top-level key beside `servers:` or `hooks:` is still ignored (for [#822](https://github.com/Tencent/teamai-cli/issues/822)).
- The closing line of `teamai recall` output is in English (for [#822](https://github.com/Tencent/teamai-cli/issues/822)).
+15
View File
@@ -174,4 +174,19 @@ describe('Swift AST structural extraction (web-tree-sitter WASM)', () => {
const memberCall = result.callSites.find((c) => c.receiver === 'shape');
expect(memberCall?.calleeText).toBe('shape.draw');
});
it('keeps the process alive while V8 would tier up the Swift grammar', async () => {
// Node 24 aborts the whole process with "Fatal process out of memory: Zone"
// shortly after a Swift parse, from V8's background optimizing compile of
// the grammar (nodejs/node#63421). Make the grammar hot, then give that
// compile time to run.
const source = 'import Foundation\nstruct Point: Equatable { func norm() -> Int { return 1 } }\n'.repeat(50);
for (let i = 0; i < 3; i++) {
await extract(source);
}
await new Promise((resolve) => setTimeout(resolve, 1500));
const { result } = await extract(source);
expect(result.stats.filesParsed).toBe(1);
});
});
@@ -1,4 +1,5 @@
import { createRequire } from "node:module";
import { setFlagsFromString } from "node:v8";
import { Language, Parser, Query } from "web-tree-sitter";
@@ -42,7 +43,28 @@ export async function ensureAstReady(): Promise<void> {
return initPromise;
}
const NO_WASM_FUNCTION_INDEX = 2 ** 31 - 1;
/**
* On Node 24+, keep tree-sitter grammars on V8's baseline Wasm tier.
*
* V8's optimizing Wasm compiler there exhausts its Zone memory on grammar code
* and aborts the process with "Fatal process out of memory: Zone"
* (nodejs/node#63421); the Swift grammar hits it right after its first parse.
* The filter allows tier-up only for a function index no grammar has.
* --liftoff-only does the same, but Node 24 ignores it when set at runtime.
* The flag is process-wide, so any other Wasm (such as undici's HTTP parser)
* stays on the baseline tier too. Older runtimes keep tier-up, which parses
* about 1.6x faster.
*/
function keepWasmOnBaselineTier(): void {
if (Number(process.versions.node.split(".")[0]) >= 24) {
setFlagsFromString(`--wasm-tier-up-filter=${NO_WASM_FUNCTION_INDEX}`);
}
}
async function initAst(): Promise<void> {
keepWasmOnBaselineTier();
await Parser.init({
locateFile: () => require.resolve("web-tree-sitter/tree-sitter.wasm")
});