FreeBSD www/node24 fails to build

FreeBSD www/node24 fails to build: missing SQLite session extension

TL;DR: www/node24 on FreeBSD unconditionally links against the ports databases/sqlite3 library instead of its own bundled copy, and databases/sqlite3 doesn’t expose a build option for SQLite’s session extension. Node 22+’s built-in node:sqlite module needs that extension to implement changesets/patchsets, so the link fails. There’s currently no port-level option to fix this on either side.


Environment

  • FreeBSD 14.4-RELEASE (mid-upgrade from 13.5 to 15.1)
  • Building via poudriere against a current ports tree checkout
  • www/node24-24.21.0
  • databases/sqlite3 (ports default build, no custom OPTIONS)

The failure

The build fails at the final link step of the node binary itself, with six undefined symbols, all from SQLite’s session/changeset API:

ld: error: undefined symbol: sqlite3session_delete
>>> referenced by node_sqlite.cc
>>>               node_sqlite.o:(node::sqlite::DatabaseSync::DeleteSessions())

ld: error: undefined symbol: sqlite3session_create
ld: error: undefined symbol: sqlite3session_attach
ld: error: undefined symbol: sqlite3changeset_apply
ld: error: undefined symbol: sqlite3session_changeset
ld: error: undefined symbol: sqlite3session_patchset
c++: error: linker command failed with exit code 1
gmake: *** [Makefile:143: node] Error 2

Root cause

Node.js 22 introduced a built-in node:sqlite module. Its session-tracking support (creating sessions, generating changesets and patchsets, applying changesets) is a thin wrapper directly around SQLite’s session extension — the sqlite3session_* and sqlite3changeset_* family of functions. That extension is not part of a default SQLite build; it has to be compiled in explicitly with SQLITE_ENABLE_SESSION and SQLITE_ENABLE_PREUPDATE_HOOK.

Upstream Node solves this by bundling its own copy of SQLite (under deps/sqlite/) built with those flags. The FreeBSD port takes a different approach: it hardcodes --shared-sqlite and actively deletes the bundled source tree during build, forcing Node to link against the system/ports SQLite instead:

# www/node24/Makefile
                libsqlite3.so:databases/sqlite3 \
                --shared-sqlite \
        @${RM} -r ${WRKSRC}/deps/sqlite/

There’s no fallback and no build knob to use the bundled copy instead — it’s unconditional. Meanwhile, databases/sqlite3‘s own OPTIONS_DEFINE list has no session-extension toggle at all:

OPTIONS_DEFINE=         EXAMPLES TCL THREADS
OPTIONS_DEFINE+=        ARMOR DBPAGE DBSTAT DIRECT_READ DQS EXTENSION FTS3_TOKEN \
                        FTS4 FTS5 LIKENOTBLOB MEMMAN METADATA NORMALIZE NULL_TRIM RBU SECURE_DELETE \
                        SORT_REF STATIC STMT TRUSTED_SCHEMA UNKNOWN_SQL UNLOCK_NOTIFY \
                        UPDATE_LIMIT URI URI_AUTHORITY

(EXTENSION here refers to SQLite’s dlopen-based loadable-extension support — a different feature entirely from the session extension.)

The result: as shipped, these two ports are incompatible for this one specific, still-experimental Node feature. This isn’t a local misconfiguration — there’s no options combination on either port that resolves it.

Impact assessment

Before treating this as blocking, it’s worth checking what actually depends on node24 on the affected system:

$ pkg query %rn node24
yarn-node24
rubygem-execjs

Neither of these touches node:sqlite. yarn-node24 just needs a working Node runtime to execute JavaScript; rubygem-execjs is a generic “run some JS via whatever runtime is available” shim for Ruby. Unless something on a given system explicitly calls the new node:sqlite session/changeset APIs, this failure is safe to defer — retry everything else and come back to it separately.

Possible fixes (not yet applied upstream)

  1. Add a build option to databases/sqlite3 exposing SQLITE_ENABLE_SESSION/SQLITE_ENABLE_PREUPDATE_HOOK. Safe in principle — the session extension only adds new exported symbols, it doesn’t change existing SQLite behavior for other consumers of the library.
  2. Add a knob to www/node24 to fall back to its bundled SQLite copy instead of unconditionally forcing --shared-sqlite, at least when the system SQLite lacks the session extension.
  3. As a local, unofficial workaround: patch databases/sqlite3‘s CFLAGS in a private ports overlay to add the two -D flags directly, without waiting for an upstream options change.

Status

No existing FreeBSD Bugzilla report was found covering this exact combination at the time of writing. Filing one against www/node24 (category “Individual Port(s)”) would be the appropriate next step for anyone who needs this resolved rather than deferred.

Written up during a FreeBSD 13.5 → 15.1 upgrade using poudriere for package rebuilds. Details (port revisions, exact error text) reflect the ports tree snapshot in use at the time and may have changed since.