Audience: anyone who installed neural-trader between v2.5.0 (Nov 2025) and v2.7.5 (today) and wondered why nothing worked.
neural-trader@2.7.6 is on npm. It fixes a four-bug cascade that made the package nonfunctional from v2.5.0 onward:
- (Reported in ruflo#1974) An install-hook fork-bomb that consumed 120 GB of RAM in 80 minutes on Apple Silicon. Fixed in 2.7.2.
- A
.gitignoremistake that silently dropped three load-bearing JS modules from the published tarball, sorequire('neural-trader')always threwCannot find module. Fixed in 2.7.5. - The Rust crate that produces the native binding depended on a hash library with x86-only build flags, so it refused to compile on Apple Silicon. Fixed in 2.7.5.
- The published npm tarball claimed it shipped binaries for five platforms but actually only contained one (
linux-x64-gnu). The CI workflow that was supposed to produce the others had multiple broken steps. Three of five fixed in 2.7.6 (linux-x64, darwin-x64, darwin-arm64).
If you tried npm install neural-trader before today and gave up, try npm install neural-trader@2.7.6 now.
neural-trader is an npm package that wraps a Rust trading engine. Most of its functionality is written in Rust and exposed to Node.js via napi-rs — a build system that compiles Rust code into a .node file (essentially a native shared library) and ships one binary per (operating system, CPU architecture) combination. When you require('neural-trader'), the JS entry point figures out which binary you need (darwin-arm64, linux-x64, etc.) and loads it.
The package therefore has two main moving parts:
- The JS layer — entry point, platform-detection loader, validators, CLI wrappers. About a thousand lines of JS spread across
index.js,src/cli/lib/*-wrapper.js, and a couple of helper modules. - The native binaries — one
.nodefile per platform, compiled fromneural-trader-rust/crates/napi-bindings/against a workspace of ~15 Rust crates (neural networks, market data, strategies, risk, execution, etc.).
For the package to "work" on a given platform, both layers have to be present in the tarball. Between v2.5.0 (Nov 2025) and v2.7.4 (today), neither was complete.
The story starts on Apple Silicon. A user runs npm install neural-trader@2.7.1. The package's install lifecycle script (scripts/install-with-prebuilds.js) runs and decides it can't find a prebuilt binary for darwin-arm64. Instead of failing gracefully, it tries to "self-heal" by running:
execSync('npm install', { stdio: 'inherit' })…from inside its own install hook. That re-invokes npm install, which re-fires the install hook, which runs npm install again. Each level forks a new node process and a new npm tree walk. The recursion is unbounded.
The reporter observed 3,049 stuck node/npm processes consuming ~120 GB of RAM in 80 minutes before manually killing them. On Linux-x64 the bug never showed up because a prebuilt binary was always found on the first pass.
Fix: the install hook was rewritten as a no-side-effect advisor — it inspects the platform, prints what binaries are/aren't available, and exits 0. It never re-spawns npm install. Shipped in neural-trader PR #109 as neural-trader@2.7.2. A CI guard (scripts/test-install-safety.js) in the repo prevents the pattern from coming back: it statically audits lifecycle scripts for execSync('npm install') and fails the build if found.
After 2.7.2 shipped, anyone calling require('neural-trader') to use it as a library still got:
Error: Cannot find module './src/cli/lib/napi-loader-shared'
Require stack:
- node_modules/neural-trader/index.js
This had been happening on every platform since neural-trader@2.5.0 (commit ae414f2, Nov 18 2025). The 2.5.0 commit was titled "Major code refactoring and A+ grade roadmap implementation" and extracted what used to be inline platform-detection logic into three shared modules:
src/cli/lib/napi-loader-shared.js— picks the right.nodefile for the hostsrc/cli/lib/napi-loader.js— back-compat wrapper for older entry pointssrc/cli/lib/validation-utils.js— input validators used bycli-wrapper.jsandmcp-wrapper.js
The refactor require()'d all three from index.js — but the diff for that commit shows none of the three files were ever staged. The author thought they'd added them; git status showed clean; the package shipped without them; every consumer since then got MODULE_NOT_FOUND.
Root cause: the repo's .gitignore had this line at the top, left over from a Python project template:
lib/
That rule is unscoped — it matches any directory called lib anywhere in the tree. So src/cli/lib/ matched, and git add src/cli/lib/*.js silently ignored every file. The git CLI does print a warning, but only if you pass an exact filename — bulk adds (git add -A, git add src/) skip the ignored paths quietly.
Fix (PR #111):
- Recreated all three missing modules. The semantics of
loadNativeBinding(packageRoot, context)were derived fromindex.js@ae414f2~1(the pre-refactor inline loader). The validators match the patterns the wrappers were already using. - Scoped the gitignore rule to
/lib/(anchored to repo root) so it can't shadowsrc/cli/lib/again.
The path-resolution logic in the new loader avoids the original API design wart: it resolves candidate paths from __dirname so it doesn't matter what packageRoot value the caller passes. The package layout is fixed; the loader leans on that fact.
While auditing the source, I went to build the Rust workspace on darwin-arm64 (an M-series Mac) and got:
fasthash-sys@0.3.2: clang: error: unsupported option '-maes' for target 'arm64-apple-macosx15.0.0'
clang: error: unsupported option '-mavx' for target 'arm64-apple-macosx15.0.0'
clang: error: unsupported option '-mavx2' for target 'arm64-apple-macosx15.0.0'
fasthash is a Rust crate that wraps several non-cryptographic hashing algorithms via a -sys shim. That shim's build.rs hard-codes x86-only SIMD compiler flags. The crate is unmaintained (last release 2019) and the upstream has open issues asking for ARM support that have been sitting since 2020.
Looking at how fasthash was actually used in neural-trader, it turned out to be one call:
// crates/neural/src/storage/agentdb.rs — placeholder embedding hash
let hash = fasthash::murmur3::hash32(text.as_bytes());…and the comment right above it noted this was a placeholder until a real embedding model was wired in. The output is non-load-bearing — it's hash-bucketed into a fixed-size float vector for stub embeddings.
Fix: replaced with stdlib DefaultHasher (SipHash), which compiles cross-platform without any C build step. The Rust workspace now builds cleanly on aarch64-apple-darwin.
I removed fasthash from Cargo.toml and dropped its orphaned entries from Cargo.lock. Tested locally — the napi-bindings build for darwin-arm64 produced a 6.3 MB Mach-O 64-bit arm64 binary, and require('neural-trader') returned 194 working exports.
The package.json#files array advertised five platform binaries:
"neural-trader-rust/neural-trader.linux-x64-gnu.node",
"neural-trader-rust/neural-trader.darwin-arm64.node",
"neural-trader-rust/neural-trader.darwin-x64.node",
"neural-trader-rust/neural-trader.win32-x64-msvc.node",
"neural-trader-rust/neural-trader.linux-arm64-gnu.node",In practice, only linux-x64-gnu was ever in the tarball. The other four .node paths were entries that the loader hopefully tried — and every consumer outside Linux x64 got MODULE_NOT_FOUND.
The reason: the GitHub Actions workflow that was supposed to produce those binaries (rust-release.yml, plus the supporting build-napi.yml and napi-build.yml) had eight separate broken steps, each enough on its own to block the pipeline:
| # | Workflow | Bug |
|---|---|---|
| 1 | rust-release.yml |
Missing permissions: contents: write at workflow level → Resource not accessible by integration |
| 2 | rust-release.yml |
Publish step copied artifacts into npm-package/bin/, but napi-rs's loader looks for the .node files next to index.js inside neural-trader-rust/. Result: even the runs that "succeeded" published tarballs with zero .node files. |
| 3 | Multiple | actions/upload-artifact@v3 / actions/download-artifact@v3 are GitHub-deprecated since April 2024. Any job using them auto-fails at "Set up job" before reaching real work. (5 workflows had this.) |
| 4 | build-napi.yml |
aarch64-unknown-linux-gnu cross-build aborted at openssl-sys: "Could not find directory of OpenSSL installation". The workflow installed gcc-aarch64-linux-gnu but no arm64 libssl. |
| 5 | build-napi.yml |
"Copy binary to packages" step searched target/<triple>/release/ for the freshly-built .node — but napi-rs writes the binary into the crate directory, not the target tree. |
| 6 | napi-build.yml |
Top-level npm ci pulled better-sqlite3 / hnswlib-node as transitive deps. Inside the napi-rs Debian Docker image (glibc 2.31), those crates fail to compile with pthread_cond_clockwait undefined (a glibc 2.30+ symbol). |
| 7 | quality-gates.yml (Gate 4/8) |
cargo test ran from the repo root, but the workspace is at neural-trader-rust/. Fails immediately with "could not find Cargo.toml in any parent directory". |
| 8 | quality-gates.yml (Gate 4/8 on Windows) |
Inline scripts used if [ $? -ne 0 ] — bash syntax — and PowerShell (Windows' default shell) rejected them with ParserError at line 1. |
PR #111 fixed all eight. The most interesting one was #6: the lts-debian napi-rs Docker image has a glibc that's a few years older than the pthread_cond_clockwait symbol referenced by bits/std_mutex.h from gcc 11's libstdc++ headers. The fix wasn't to upgrade glibc — it was to stop installing those native deps at all in that job (the napi-bindings crate doesn't need them).
After PR #111 merged, I produced the missing binaries by:
- Building
darwin-arm64locally on this Apple Silicon machine. - Cross-building
darwin-x64from the same host viarustup target add x86_64-apple-darwinandnapi build --target x86_64-apple-darwin. - Inheriting the existing
linux-x64-gnubinary from the previous release.
Published as neural-trader@2.7.6 (PR #112).
linux-arm64-gnu and win32-x64-msvc are still pending. The local cross-build path needs to vendor openssl-sys (it's pulled in transitively through reqwest → hyper-tls → native-tls → openssl-sys from several internal crates), which is either a TLS-backend swap to rustls or a workspace-wide [patch.crates-io] override — both substantial enough that CI is the better path. The CI workflow is now fixed and will fill those in on the next tag-triggered release.
If you've previously tried neural-trader on macOS or Windows and given up:
# Recommended even though the install hook is now safe — defense in depth
npm install --ignore-scripts neural-trader@2.7.6…will work on linux-x64, darwin-x64, and darwin-arm64. On linux-arm64 and win32-x64 the JS surface still installs cleanly (no fork-bomb, no missing modules), but calls into the native engine will throw at runtime — track the next release for full coverage.
The Ruflo ruflo-neural-trader plugin will be bumped to require neural-trader@^2.7.6 in a follow-up.
- Original bug report: ruvnet/ruflo#1974
- PRs:
- ruvnet/neural-trader#109 — install hook safety (shipped 2.7.2)
- ruvnet/neural-trader#111 — fasthash, missing loader modules, gitignore, 8 CI fixes (shipped 2.7.5)
- ruvnet/neural-trader#112 — release 2.7.6 with darwin-x64 binary
- Published versions:
npm view neural-trader@latest→2.7.6
After 2.7.6 closed the four originally-reported bugs (#1974, missing loader, fasthash, missing binaries), a follow-up audit found that the CLI commands the downstream ruflo-neural-trader plugin documented didn't actually exist. npx neural-trader --backtest --strategy momentum --symbol AAPL returned Unknown command. The published neuralBacktest() Rust function returned identical placeholder metrics regardless of input (verified empirically across AAPL/MSFT/GOOGL/TSLA/NVDA — all returned the same alpha=0.123, sharpe=3.12, annualized_return=0.342).
The 2.8.x line addresses this by building a flag-style dispatcher in JS (src/cli/legacy-flags.js) that handles every documented command with real math, fed by real Yahoo Finance data when --live is passed (no API key needed — uses the public chart JSON endpoint with fetch).
| Version | What it added |
|---|---|
| 2.7.6 | Closed the original 4 bugs; 3/5 platform binaries shipping |
| 2.7.7 | Fixed bin/cli.js requiring src/cli/program.js for --version — short-circuited to the package.json read |
| 2.7.8 | Moved cli-table3 + ora from devDependencies → dependencies |
| 2.7.9 | Fast-path --version/--help direct in bin/cli.js (avoids loading missing src/lib/* helpers) |
| 2.7.10 | Friendly "not wired up" error for mcp/agent/deploy subcommands with missing helpers |
| 2.8.0 | New src/cli/legacy-flags.js — dispatcher for --backtest/--signal/--risk/--portfolio/--regime/--strategy-create/--train/--predict/--evaluate |
| 2.8.1 | Switched --backtest/--train/--predict from BacktestEngine (which returned 0 trades without OHLCV) to neuralBacktest Rust call |
| 2.8.2 | Disclosed upstreamStub: true on neural* outputs (since they're hardcoded placeholders) |
| 2.8.3 | Real backtest math in JS: SMA20/SMA50 crossover (momentum) or RSI mean-reversion, with proper Sharpe/Sortino/drawdown/winRate/profitFactor computation from a day-by-day simulation. Different symbols now produce different metrics. |
| 2.8.4 | --live flag: real Yahoo Finance OHLCV via query1.finance.yahoo.com/v8/finance/chart/<symbol>. Falls back to deterministic synthetic on rate-limit/offline. |
| 2.8.5 | --walk-forward (rolling 6m-train/1m-test), --monte-carlo --mc-runs N (Gaussian resample with p5/p25/p50/p75/p95), multi-symbol --symbols A,B,C with aggregate |
| 2.8.6 | --signal scan returns real direction/confidence/indicator readings instead of empty |
| 2.8.7 | --benchmark <SYM>: beta = cov/var, alpha annualized, correlation, excessReturn vs buy-and-hold |
| 2.8.8 | --optimize --param "name:min:max:step" grid search (capped at 60 grid points). Recognizes fast_ma, slow_ma, rsi_period, entry_z. |
| 2.8.9 | Kelly criterion fraction in backtest metrics: f* = (b·p − q) / b clamped to [0,1] |
| 2.8.10 | Pairs trading (z-score of normalized spread, 30-day rolling window, ±2σ entry, ±0.5σ exit) + adaptive strategy (regime-switching) |
| 2.8.11 | Multi-indicator strategy: pure-JS MACD + Bollinger combined with RSI in 2-of-3 voting rule |
$ npx neural-trader --backtest --symbol AAPL --live \
--period 2024-01-01..2024-12-31 \
--benchmark SPY
{
"strategy": "momentum",
"symbol": "AAPL",
"dataSource": "yahoo-finance", // 251 real daily bars
"metrics": {
"totalReturn": 0.270, // +27.0%
"sharpeRatio": 1.529,
"maxDrawdown": 0.109,
"winRate": 0.667,
"kellyFraction": 0.642
},
"benchmark": {
"benchmark": "SPY",
"alpha": 0.183, // beta-adjusted, annualized
"beta": 0.755,
"correlation": 0.495,
"strategyTotalReturn": 0.270, // +27%
"benchmarkTotalReturn": 0.141, // +14.1% (real S&P 500 2024)
"excessReturn": 0.129
}
}
The ruflo-neural-trader plugin now has a 20-gate runtime smoke that exercises every documented command end-to-end against a fresh install of the published package. It runs on every plugin PR + weekly cron (the neural-trader-smoke workflow in .github/workflows/neural-trader-smoke.yml).
- Unit:
tests/cli/unit/legacy-flags.test.js(10 cases, plainnode:assert, no jest install) — runs on every PR via.github/workflows/cli-unit-tests.yml - Integration: ruflo's
runtime-smoke.sh(20 gates, installs fresh from npm)
linux-arm64-gnuandwin32-x64-msvcbinaries (requires cross-compile or CI-built artifacts)- Real implementations of
neuralBacktest/neuralTrain/neuralPredict/neuralEvaluatein the Rust crate (theupstreamStubflag will go away when those exist) - Live broker integration (
trader live --broker ...)