Skip to content

Server crashes on startup if any single tree-sitter language parser fails to load (e.g. unsigned darwin-arm64 tree-sitter-cpp/scala prebuilds) — even for codebases that don't use that language #421

Description

@murad-mocart

Problem

packages/core/src/splitter/ast-splitter.ts requires every supported language's tree-sitter grammar unconditionally at module load time:

const JavaScript = require('tree-sitter-javascript');
const TypeScript = require('tree-sitter-typescript').typescript;
const Python = require('tree-sitter-python');
const Java = require('tree-sitter-java');
const Cpp = require('tree-sitter-cpp');
const Go = require('tree-sitter-go');
const Rust = require('tree-sitter-rust');
const CSharp = require('tree-sitter-c-sharp');
const Scala = require('tree-sitter-scala');

None of these are wrapped in try/catch, so if any single one fails to load — for any reason, on any platform — the whole MCP server process crashes on startup, for every user, regardless of what languages their codebase actually uses.

Concrete case that hit this

On macOS arm64 (Node 24), the darwin-arm64 prebuilt binaries for tree-sitter-cpp and tree-sitter-scala are unsigned (codesign -dv → "code object is not signed at all"), while every other language's darwin-arm64 prebuild (rust, java, go, python, typescript, javascript, c-sharp, the tree-sitter core package itself) is properly signed. dlopen refuses to load the unsigned binary and throws ERR_DLOPEN_FAILED ("code signature invalid"), which propagates as an uncaught exception and kills the process before the MCP server even starts — for a TypeScript/JavaScript-only codebase with zero C++/Scala files.

Error: dlopen(.../tree-sitter-cpp/prebuilds/darwin-arm64/tree-sitter-cpp.node, 0x0001):
tried: ... (code signature invalid in ... errno=85 ...)

Attempting to locally ad-hoc re-sign the binary (codesign --force -s -) also fails ("main executable failed strict validation"), suggesting the binary itself has some structural issue from its build/cross-compile pipeline, not just a missing signature.

Impact

  • The server is a hard, unrecoverable crash for affected users — not a degraded/partial-functionality situation. No amount of EMBEDDING_PROVIDER/ignorePatterns/etc. configuration works around it, since it never gets past module load.
  • This is entirely disconnected from the user's actual codebase — a pure-TypeScript repo crashes just as hard as a C++ one, because every parser loads eagerly regardless of use.
  • The specific unsigned-binary issue is arguably a bug in the upstream tree-sitter-cpp/tree-sitter-scala packages (not something this repo controls directly), but the blast radius here is self-inflicted: any single broken language parser (this one, or a future one, on any platform) takes down the entire server for all languages.

Suggested fix

Make per-language grammar loading lazy and defensive — either:

  1. require() each grammar inside a try/catch at first use (when a file of that extension is actually encountered), logging a warning and disabling AST-splitting for that language on failure, falling back to the langchain character-based splitter for those files; or
  2. At minimum, wrap the top-level requires in try/catch and only register the languages that loaded successfully in the extension→parser map, rather than requiring the whole module to fail.

Either would have let this codebase (no C++/Scala files) index successfully without ever touching the broken parsers.

Workaround we used locally: ad-hoc-patched the two packages' bindings/node/index.js to a no-op stub (module.exports = { nodeTypeInfo: {} }) since they're never invoked for a language absent from the codebase — but this obviously isn't something an end user should have to do to install and run the server.

Metadata

Metadata

Assignees

No one assigned

    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