Bun is an explicit opt-in runtime for standalone Apus CLI, Gateway, and managed node host installations. Node remains their primary and recommended runtime. The native macOS app and fresh local Tauri installations on Linux use the Apus Bun fork for their app-managed runtime. This reference covers Bun requirements and compatibility; see Bun for standalone installation and opt-in steps, or Node.js compatibility for Node requirements.

Plugin resolution stays with Bun's native/Jiti loader and Bun.plugin on Bun, even when Module.registerHooks is available; Node uses Module.registerHooks. Windows paths alone do not force native loading: captured source uses the same capability-based selection as other platforms, and retiring a plugin generation removes its native and Jiti cache records. Native SDK aliases return platform-native filenames from require.resolve(); Jiti transformation maps retain their own path spelling.

Requirements

Node fallback and build processes require Node 24.22.1+ or 26.12.1+ for the eMudhra compromised-key rejection fix. Updating Node does not change Bun's independent TLS verifier or its release qualification.

Apus requires Bun 1.4.0+, an available node:sqlite API, and the same WAL-safe SQLite floor as Node.

Platform SQLite library Bun uses Extension loading What Apus does
Linux Statically linked SQLite; 3.53.2 in Bun 1.4.2 Supported No additional library setup needed.
macOS Apple system SQLite by default Unavailable in Apple's library Automatically selects a suitable library; see below.
Windows Same static SQLite build as Linux Supported No additional library setup needed.

The platform defaults come from Bun's SQLite build policy; the Bun 1.4.2 version definition pins SQLite 3.53.2.

macOS app private runtime

Both desktop apps and CI consume the single scripts/lib/apus-bun.json pin. Every repin requires both CI's paired Bun replay and Bun-only smoke, and the native macOS app's checks and two-binary test set; a failure in either blocks the pin for all consumers. See shared runtime pin. The Linux Tauri app leaves existing Gateway services unchanged on startup and updates. Switching to its current bundled Bun requires Use bundled runtime…; see explicit runtime selection. macOS Tauri keeps its existing runtime behavior, separate from the native macOS app. Windows Tauri retains its existing runtime until a signed fork Windows build is available; an unsigned dry-run is not shippable.

Apus.app bundles a pinned Apus Bun fork, the full matching Apus package, and a signed SQLite library that meets the WAL safety floor and supports extension loading. Its private node worker and Chrome-extension setup run on that Bun executable with APUS_SQLITE_LIBRARY pointing to the bundled library. These helpers need no Node or Homebrew SQLite installation. Bundled native libraries remain Team-signed; only the Bun executable disables library validation to load runtime-installed plugin addons. See the signing tradeoff.

The package includes the CLI, Gateway, Control UI, npm, and sqlite-vec. Fresh local profiles use the bundled Gateway. Eligible app-managed Node services migrate through the installed updater before a same-version switch to Bun, with verified Node rollback. Independently managed services and saved operator runtime pins remain with their existing owner. See Gateway on macOS and macOS developer setup.

SQLite library selection on macOS

For external Bun installations, install Homebrew SQLite for native sqlite-vec KNN memory queries. The macOS app's private runtime already includes its own library.

brew install sqlite

Before opening databases, Apus selects a library in this order:

  1. An explicit library path supplied internally, otherwise APUS_SQLITE_LIBRARY.
  2. $HOMEBREW_PREFIX/opt/sqlite/lib/libsqlite3.dylib.
  3. /opt/homebrew/opt/sqlite/lib/libsqlite3.dylib.
  4. /usr/local/opt/sqlite/lib/libsqlite3.dylib.
  5. /opt/local/lib/libsqlite3.dylib (MacPorts).

Candidates must meet the WAL safety floor and support extension loading before selection. If automatic discovery finds no qualifying library, Bun keeps its runtime library; ordinary agent databases can open if that library meets the WAL floor. The memory KNN child uses the same selected library.

SQLite storage workers inherit the main process's selected library. Opening another database or restarting a storage worker reuses that selection without repeating Bun's one-shot library initialization.

Package-update recovery retains the library selected during Bun admission. On macOS, copy the printed recovery command including its APUS_SQLITE_LIBRARY prefix; it works from a fresh shell without the service environment or custom HOMEBREW_PREFIX. If recovery cannot meet the SQLite safety floor, it refuses before opening the journal and names the recorded library input to restore. The version-1 recovery journal format is unchanged.

Set APUS_SQLITE_LIBRARY in the process environment before starting Apus to override discovery:

APUS_SQLITE_LIBRARY=/path/to/libsqlite3.dylib bun apus.mjs gateway

On macOS, apus gateway install --runtime bun, apus node install --runtime bun, and wrapper-based installs persist APUS_SQLITE_LIBRARY and HOMEBREW_PREFIX from the installing shell into the managed service definition, so the service selects the same library. To change these values for an already-installed service, reinstall with apus gateway install --runtime bun --force (or apus node install --runtime bun --force for a managed node host) from a shell with the desired values; a bare reinstall of an already-loaded service is a no-op. Direct Node-runtime services never persist them.

An invalid override fails with:

Cannot use SQLite library <path>: <reason>. Fix or unset APUS_SQLITE_LIBRARY; install a supported library with brew install sqlite.

Node and non-macOS Bun ignore this override, with a warning in Gateway startup logs. When a library is selected, Gateway startup logs SQLite: using <path> (<version>, extension loading enabled). apus doctor reports the selection for the doctor process.

Daemon install, apus gateway start repair, apus doctor, and service audits check candidate Bun executables through the same selection, so they judge and report the library the Gateway will actually open rather than Bun's runtime SQLite. An invalid override fails those checks with the message above instead of advising a Bun upgrade or switching the service to Node.

If you previously used a preload that calls Database.setCustomSQLite(), remove it and set APUS_SQLITE_LIBRARY to the same path instead. The hook is one-shot: keeping the preload causes SQLite already loaded, even if both selections name the same library. Apus's override also forwards the path to the KNN child.

Memory search without an extension-capable library

When the KNN child cannot load extensions, memory search falls back to a batched embedding scan. It preserves provider and source filters and cancellation checks between batches, but can be slower on large indexes. See Memory configuration.

Browser subprocesses

The browser plugin starts its helper processes with the Bun executable that runs Apus, so browser automation needs no separate Node installation:

Bun-only installs

Trusted Bun-only global installs on macOS and Linux install an apus shell launcher in Bun's existing global bin directory (bun pm bin -g). It records the absolute Bun executable from APUS_PACKAGE_BUN_LAUNCHER and the installed package entry point, so apus --version, apus status --json, and Gateway commands work without Node. Add that bin directory to PATH. The npm package's Node shebang and Node installs remain unchanged; Windows Bun launchers are not supported yet.

Pin the Gateway service to your Bun executable so updates and Doctor retain it:

apus gateway install --runtime bun --runtime-path <bun> --force

Update, repair, and Doctor maintenance children use the running Bun executable. Bun package-manager checks and installs use an explicit executable: the verified service Bun when updating its root, otherwise process.execPath when the updater runs under Bun, then bare bun from PATH as the final fallback. This preserves the selected Bun even when PATH has no Bun or contains a different build.

When an owned managed Bun Gateway serves a different package root from the CLI, apus update advances the Gateway installation in place and leaves the invoking CLI installation unchanged. The updater validates that service's actual Bun for Bun 1.4+ and WAL-safe node:sqlite, without comparing its emulated Node version to engines.node. If the updater runs on Node, that Node must also meet the target package's Node and SQLite requirements because finalization uses it. The existing service install/restart path retains the recorded Bun pin. Node split-root routing is unchanged, and a path under ~/.apus alone does not establish Bun global-install ownership.

Doctor and apus update repair from another installation leave this Bun Gateway at its own root. Explicit repair reports the installation drift and refuses maintenance before stopping the service. Use <bun> <service-root>/apus.mjs update repair or <bun> <service-root>/apus.mjs doctor --fix for repair from the service's installation.

First installs and updater staging without a persistent Node require APUS_PACKAGE_BUN_LAUNCHER set to the absolute Bun executable that launches the CLI. The updater sets it automatically when running under Bun; an app must set it for its first bun add -g --trust apus@<version>. Preinstall validates that launcher as Bun 1.4+ without spawning absent or nonexecutable Node candidates. Without the marker, preinstall still requires a persistent Node; a Node found on PATH must satisfy the package's Node requirements even when the marker is set.

The trusted package lifecycle creates the launcher only when Bun's existing global bin points to that package and no persistent Node is present. Updates create it in the private staging bin, then relocate and publish it with the package; rollback restores the previous launcher. Reinstalling the package refreshes the recorded Bun path. A lifecycle warning does not abort an otherwise usable package update.

Launcher paths are literal data: spaces, apostrophes, double quotes, dollar signs, backticks, backslashes, and globs work in both staged and final installation paths. Released updaters can relocate the raw path bytes without turning them into shell code. The launcher uses shell builtins to read its own data lines, then exec to preserve arguments, stdin, exit status, and signals without a wrapper process, subprocess, or temporary file.

The renderer requires absolute paths without NUL, newline, or carriage return. For unsupported paths, installation leaves Bun's original symlink unchanged and Doctor reports the reason without offering launcher repair. Invoke <bun> <package-root>/apus.mjs directly, with shell quoting as needed, or use single-line paths. If a released updater introduces a newline into a final path, the split data is never executed: the launcher exits 127 with a target-not-found message. Carriage-return paths remain unsupported even if a released updater inserts one; the strict launcher parser does not adopt that modified launcher.

If the launcher is missing, relinked, or still names a moved Bun executable, run <bun> <package-root>/apus.mjs doctor --fix from that installation. Doctor reports the problem and uses its existing repair consent rules. It preserves commands belonging to another installation. Custom Bun global-bin settings must be available to the installing process and Doctor; a one-off bun --config argument is not inherited by package lifecycle children. bunx --bun apus selects Bun for that invocation only, not for the plain shell command.

install-cli.sh still provisions Node and uses its existing npm or Git install path. The macOS app owns its own launcher separately. It can reuse this POSIX data-line launcher contract, executable mode, and atomic publication at its existing CLI location. The app must regenerate the launcher when its runtime or package root changes.

Bun's uninstall cleanup removes dangling symlinks but can leave a generated shell launcher behind; see Remove the CLI.

Published updaters through 2026.9.6 cannot update a Bun-only install. They do not set this marker, so the new package's preinstall stops staging (global-install-failed). If the caller sets the marker, their own bare node check fails to start instead (update-executor-settlement-failed). Both refusals happen before the Gateway stops, and it keeps running. A fixed version must drive the update; installing a fixed candidate cannot change the updater already running.

The installed updater runs first. In a Linux split-root fixture, published 2026.9.6 refused early with ENOENT when Bun was absent from PATH, leaving the Gateway and both installations unchanged. With the fork Bun on PATH, the same published driver updated the Gateway installation in place and restarted it healthy while leaving the invoking CLI unchanged. The routing and explicit Bun selection described above apply from the first updater containing the fix; a newer candidate cannot change the installed updater's first-hop behavior.

Npm-sourced plugins use Apus's bundled npm 12.1.0 CLI under Bun and do not require a separate Node or npm installation.

SQLite worker lifecycle

After selecting SQLite, long-lived Gateway, node, and worker hosts that own SQLite pools on macOS and Linux await a check of whether Bun's ordinary DatabaseSync.close() and Symbol.dispose() release native statements and WAL resources. The check runs once in a small isolated worker; it uses behavior, not the Bun version string. Short CLI paths do not run it. A passing result enables the shared writer pool and targeted reader cleanup used on Node.

Stock Bun 1.4.2 fails this check and retains the conservative lifecycle below. Errors, timeouts, and unsupported WAL behavior also keep that lifecycle and record the reason. A timeout or error returns the conservative decision without waiting for worker cleanup. Cleanup waits up to five seconds for worker exit before removing its private files. An unconfirmed exit leaves the worker unreferenced and its directory intact, with a SQLITE_CLOSE_PROBE_CLEANUP warning naming the retained path. A passing check also uses this bounded join before accepting success. Windows Bun stays conservative without running the check until Windows conformance is qualified. Before the result is available, per-operation readers and close paths use conservative cleanup without fixing the global decision; later operations can use the completed result. Each writer broker fixes placement, native-stop acknowledgement, and retirement policy together when it is created. An early conservative broker keeps that policy even if the check later passes, with a one-time SQLITE_EARLY_TOPOLOGY warning explaining its retained layout. Workers inherit the decision at creation; those started before it completes remain conservative for their lifetime, while later workers inherit the completed result.

On conservative runtimes, a successful service runtime-pin inspection can leave private snapshot files pending cleanup until process exit. Apus records a cleanup warning and retains ownership of those files without failing the inspection. Read errors and required-cleanup failures still stop the operation; the original state database is unchanged.

The result is never saved in config or state. Each new long-lived host checks its selected runtime and library again, including after an upgrade, downgrade, or rollback. No schema or data migration is needed, and the installed updater is unchanged. An inconclusive capability check does not block an otherwise supported runtime; the existing runtime and SQLite safety requirements still apply.

For internal comparisons, set APUS_DIAGNOSTICS=sqlite.close.conservative before starting Bun. This exact normalized entry skips the check and records a decided conservative result with its reason; wildcard or all-diagnostics entries do not change the policy. It cannot force a positive capability, does not affect Node's capability, and is read only from the process environment, never saved in config or state. Gateway startup logs include the decision and its reason.

Known limitations

See Bun for the workflow and lifecycle trust commands.

History across releases

Release Change
Unreleased (main) Reuses SQLite workers after a native-close check passes on macOS or Linux. Stock Bun 1.4.2, Windows, and inconclusive checks retain conservative cleanup.
Unreleased (main) Runs the macOS app's private worker and Chrome setup on the Apus Bun fork, bundling the full package and signed extension-capable SQLite in one shared runtime. Gateway hosting remains external.
Unreleased (main) Headless node updates on macOS and Linux fetch and verify registry archives in-process and prepare private runtimes with Bun, without Node or npm. Windows preparation still requires npm. #160575
Unreleased (main) Updates owned split-root Bun Gateway installations in place, retains their runtime pins, and uses explicit Bun executables for package-manager checks and installs.
Unreleased (main) Keeps Bun maintenance children and service runtime selection, and adds APUS_PACKAGE_BUN_LAUNCHER for preinstall validation of Bun-only installs and updater staging.
Unreleased (main) Headless node update checks read the npm registry in-process under Bun instead of running npm view. #160154
Unreleased (main) Runs the bundled npm 12.1.0 CLI under Bun for npm-sourced plugin installs, updates, and removal without a separate Node or npm installation.
Unreleased (main) Implicit Gateway and managed node host reinstalls, update refresh, and Doctor's unloaded-service reinstall retain a supported recorded Bun executable without creating a runtime pin.
Unreleased (main) Tool Search code mode (tool_search_code) is retired; structured Tool Search needs no Node under Bun.
Unreleased (main) Starts the packaged Chrome DevTools MCP server with the current runtime, so existing-session browser profiles no longer require a Node installation under Bun.
Unreleased (main) Gateway computer control runs its host worker on the Gateway's own runtime, so a Bun Gateway controls its managed desktop without an installed Node.
Unreleased (main) Uses native PTYs without Node on macOS/Linux with Terminal.pause()/resume() (Apus fork with macOS exit fix); other Bun builds keep the Node helper. Windows keeps node-pty.
Unreleased (main) Expands Bun SQLite storage from four databases to up to 64 dedicated workers within the existing 64-client cap while retaining worker-exit cleanup.
Unreleased (main) Managed Bun services on macOS persist APUS_SQLITE_LIBRARY and HOMEBREW_PREFIX from the installing shell.
Unreleased (main) Daemon install, repair, doctor, and service audits check Bun executables through the same SQLite library selection as Gateway startup, with a minimal check environment. #142186
Unreleased (main) Automatically selects a WAL-safe, extension-capable macOS SQLite library and propagates it to the memory KNN child. Adds APUS_SQLITE_LIBRARY. #141854
Unreleased (main) Documents Bun 1.4.2 retaining native statements and WAL/shared-memory files after close or disposal, with Node advised when prompt file release matters. #141846
Unreleased (main) Adds batched embedding-scan fallback when the KNN child cannot load extensions, preserving provider/source filters and cancellation between batches. #141104
Unreleased (main) Allows ordinary agent databases on SQLite builds without extension loading. Native vector search still needs an extension-capable library. #139487
v2026.8.2 Repairs Bun 1.4 authenticated Gateway WebSocket compatibility with the installed npm receiver, preserving payload limits and request scheduling. #134282
v2026.8.1 Restores explicit managed-service selection for the CLI, Gateway, and managed node host, requiring Bun 1.4.0+, node:sqlite, and WAL-safe SQLite. #129593
v2026.7.2-beta.5; stable v2026.8.1 Restores experimental CLI/Gateway support for builds providing node:sqlite, documented as 1.4.0 canary and later; the guard uses an API check without a numeric Bun minimum at this stage. #114256
v2026.7.2-beta.5; stable v2026.8.1 Documents bun install failing on the pnpm workspace layout and changes dependency instructions to pnpm install; Bun remains a script runner. #114256
v2026.7.1; main v2026.7.2-beta.1 Rejects Bun CLI/Gateway use because node:sqlite is unavailable, makes managed runtime selection Node-only, and directs legacy Bun services to Node. Package-script use remains available. #106065
v2026.1.12 Labels Bun Gateway use experimental and not recommended because of WhatsApp/Telegram bugs; recommends Node for production.
v2026.1.9 Removes Bun from the interactive daemon-runtime picker while the explicit validator still accepts Bun.
v2026.1.8 Documents Bun as an optional package-script runner for local builds/tests, with optional dependency installation at that time. pnpm remains primary.
v2026.1.8 Documents ignored pnpm lockfiles, a historical postinstall patch bridge, lifecycle trust, and scripts that invoke pnpm internally. The patch bridge is not a current install recommendation.
v2026.1.8 Introduces optional --daemon-runtime bun when WhatsApp is disabled because the Baileys WebSocket reconnect path could corrupt memory under Bun. Node remains the default and recommendation.

Related