Skip to content

tooling: migrate Node version management from nvm to Volta (multi-repo) #2508

Description

@os-zhuang

Problem

pnpm dev broke locally with two stacked failures, both traced back to how Node
is provisioned on dev/agent machines:

  1. env: node: No such file or directorynvm only puts node on PATH via
    shell functions sourced from .zshrc, which zsh only loads for interactive
    shells. Any non-interactive process (scripts, some agent/CI shells) gets a PATH
    with no node at all, even though node -v works fine in a normal terminal.
  2. better-sqlite3 ABI mismatch (NODE_MODULE_VERSION 141 vs 137) — the native
    binding had been compiled against a different Node build than the one currently
    active via nvm. Because Node version activation is per-shell and implicit, it's
    easy for one process to install/rebuild native deps against version A while
    another later runs against version B.

Neither failure is repo-code related — both come from nvm's shell-hook model,
which becomes a real liability once many agents/processes run in parallel
against the same checkout(s): whether a given subshell sourced .zshrc is
unpredictable, so "which Node am I actually running" silently varies per process.

Proposal

Replace nvm with Volta across our repos (framework + objectui

  • cloud + objectos — front and back end).

Why Volta specifically:

  • Volta installs real shim binaries into a static PATH directory (~/.volta/bin),
    not shell functions — so version resolution works identically in interactive,
    non-interactive, and sandboxed/agent shells. This is the exact failure mode (1)
    above; Volta doesn't have it.
  • Per-repo pinning lives in package.json:
    "volta": { "node": "24.18.0", "pnpm": "10.31.0" }
    Volta auto-switches on cd, so repos with different toolchain needs (front-end
    vs back-end) don't fight each other, and there's no more "whoever ran
    pnpm install last with whatever Node happened to be active" drift that caused
    failure mode (2).

Scope / open questions

  • Confirm target Node version to pin — proposal: 24.18.0 (already the version
    everyone is effectively running; no forced upgrade as part of this migration).
  • Pin pnpm version via the same volta field (already pinned via
    packageManager; Volta field should match).
  • Land "volta" field in framework/package.json first, verify in a fresh
    non-interactive shell (zsh -c "which node"), then propagate to
    objectui / cloud / objectos.
  • Update onboarding docs / README that currently reference nvm.
  • Decide whether to fully uninstall nvm from shared dev machines or just stop
    sourcing it (leave installed, unhooked, in case something else depends on it).
  • Check CI runners — do they rely on nvm today, or already pin Node another way?

Non-goals

  • Not proposing a Node version upgrade — this is purely a version-manager swap to
    fix determinism, not a bump to a newer major.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions