Skip to content

fix(heroku): pin Node 22 and provide tree-sitter CLI so deploys succeed#69

Open
Kamehameya wants to merge 2 commits into
mainfrom
fix/heroku-node-version-and-tree-sitter-cli
Open

fix(heroku): pin Node 22 and provide tree-sitter CLI so deploys succeed#69
Kamehameya wants to merge 2 commits into
mainfrom
fix/heroku-node-version-and-tree-sitter-cli

Conversation

@Kamehameya

Copy link
Copy Markdown
Contributor

Problem

Heroku deploys for the playground app have been failing on every push — 43 of 43 builds failed. The build log only shows pnpm installELIFECYCLE Command failed with exit code 1, because the buildpack runs with NPM_CONFIG_LOGLEVEL=error and suppresses the detail.

Reproducing under Heroku-like conditions surfaced two independent blockers:

1. Heroku builds on Node 26, which can't compile the parser

engines.node was >=18.0.0. The Heroku Node buildpack prioritizes engines.node over the NODE_VERSION config var and resolves the open range to the newest release (Node 26.5.0) — the buildpack log even warns about the "wide range". The tree-sitter native module fails to compile against Node 26's V8 headers:

tree-sitter@0.25.0 ... node-gyp rebuild
error: variable template partial specialization of 'is_convertible_v' ...
gyp ERR! build error — `make` failed with exit code: 2
node -v v26.0.0
 ELIFECYCLE  Command failed with exit code 1.

This is why repeatedly setting NODE_VERSION on the app never took effect.

2. heroku-postbuild needs the tree-sitter CLI, which the buildpack lacks

Once Node is pinned and pnpm install succeeds (parser.c and prebuilds are committed), heroku-postbuild runs pnpm build, which calls tree-sitter generate:

> tree-sitter generate
sh: tree-sitter: command not found
 ELIFECYCLE  Command failed with exit code 1.

Fix

  1. Pin engines.node to 22.x — matches mise.toml, CI, and the tested toolchain, and stops the buildpack from selecting Node 26.
  2. Fetch the tree-sitter 0.25.10 CLI in heroku-postbuild into .heroku-bin/ and prepend it to PATH for the build, mirroring what .github/workflows/ci.yml already does. Added .heroku-bin/ to .gitignore.

Verification

Ran the exact heroku-postbuild script, unmodified, inside the real heroku/heroku:24-build image (emulated x86_64 to match dynos), with Node 22 and NODE_ENV=production, source copied in as a fresh slug:

  • Base image already provides the C/C++ toolchain the parser needs: gcc 13.3, g++, make 4.3, python3.12, strip 2.42 (no Rust required — nothing on the build path invokes cargo).
  • pnpm install --frozen-lockfile
  • heroku-postbuild build step ✅ — 16/16 turbo tasks green
  • Output present: apps/ui/dist/index.html and apps/ui/dist/docs/index.html (what server.mjs serves) ✅

🤖 Generated with Claude Code

@salesforce-cla

Copy link
Copy Markdown

Thanks for the contribution! Unfortunately we can't verify the commit author(s): Amey Mandalik <a***@s***.com>. One possible solution is to add that email to your GitHub account. Alternatively you can change your commits to another email and force push the change. After getting your commits associated with your GitHub account, refresh the status of this Pull Request.

Heroku deploys had been failing on every push (43/43 builds). Two independent
blockers, plus a caching bug in the static server, are fixed here.

1. engines.node was ">=18.0.0". The Heroku Node buildpack prioritizes
   engines.node over the NODE_VERSION config var and resolves the open range to
   the newest release (Node 26), against which the tree-sitter native module
   fails to compile (node-gyp build error), aborting pnpm install. Pin to 22.x
   to match mise.toml/CI and the tested toolchain.

2. heroku-postbuild runs pnpm build, which invokes `tree-sitter generate`, but
   the CLI is not present on the buildpack. Download the 0.25.10 linux-x64
   binary into .heroku-bin/ and prepend it to PATH for the build, mirroring what
   .github/workflows/ci.yml already does. .heroku-bin/ is gitignored.

3. The static server's hash detection used /\.[a-f0-9]{8,}\.\w+$/, which
   requires a dot before a lowercase-hex hash. Vite emits name-<base64url>.ext
   (e.g. index-FMgzpBhJ.js), so every UI asset fell through to max-age=60
   instead of the intended 1-year immutable cache. Replace with
   isContentHashed(), recognizing both Vite (-hash) and Docusaurus (.hash/-hash)
   content hashes while excluding plain kebab-case names so unhashed files are
   never frozen in caches; HTML is always revalidated.

Verified end-to-end in the real heroku/heroku:24-build image (x86_64, Node 22,
NODE_ENV=production): 16/16 build tasks green and apps/ui/dist/{index.html,
docs/index.html} produced. Cache logic checked against all 274 built files with
0 false positives and 0 missed hashes; path-traversal regression suite passes.
@Kamehameya Kamehameya force-pushed the fix/heroku-node-version-and-tree-sitter-cli branch from 27af196 to f7033f7 Compare July 13, 2026 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants