Skip to content

fix(cli): process.exit at entry point to prevent ONNX event loop hang#1441

Open
marioja wants to merge 1 commit intoruvnet:mainfrom
marioja:memory-hang
Open

fix(cli): process.exit at entry point to prevent ONNX event loop hang#1441
marioja wants to merge 1 commit intoruvnet:mainfrom
marioja:memory-hang

Conversation

@marioja
Copy link
Copy Markdown

@marioja marioja commented Mar 25, 2026

Summary

Fixes #1428ruflo memory init, ruflo memory stats, and other memory subcommands hang indefinitely after completing because ONNX/WASM worker threads (from @xenova/transformers / all-MiniLM-L6-v2) keep the Node.js event loop alive.

Root cause

When any memory subcommand calls callMCPTool(), it triggers ensureInitialized() in memory-tools.ts, which loads the ONNX embedding model. The ONNX runtime spawns worker threads that are never terminated, preventing Node.js from exiting naturally.

Why the fix is in bin/cli.js and not in initMemoryCommand

The original inline fix (removed in this PR) placed a setTimeout(() => process.exit(0), 500).unref() inside initMemoryCommand. This was wrong for several reasons:

  1. Only fixed one command. Every memory subcommand (stats, store, search, list, cleanup, compress, etc.) triggers the same ONNX load via ensureInitialized(), so every one of them hangs. Fixing them individually means 20+ call sites, each a maintenance burden and a place to forget.

  2. Commands shouldn't call process.exit(). A command's action function is a library method — it returns a result object ({ success, data }) to its caller. Calling process.exit() inside it breaks testability (unit tests mock process.exit and the mock throws, failing the test) and violates separation of concerns. The command doesn't know if it's being called from a CLI binary, a test harness, or programmatic API usage.

  3. The entry point is the right place. bin/cli.js is the actual process boundary — it creates the CLI instance, calls cli.run(), and is responsible for process lifecycle. Adding .then(() => process.exit(0)) here means:

    • All commands benefit (memory, neural, or any future command that loads ONNX)
    • CLI.run() stays a pure library method that tests can await without side effects
    • The existing 32 unit tests continue to pass unchanged

Changes

File Change
bin/cli.js Added process.exit(0) in .then() after cli.run() resolves
src/commands/memory.ts Removed the original inline setTimeout/process.exit hack from initMemoryCommand

Testing

# Before fix: hangs until killed
timeout 10 node bin/cli.js memory stats   # exit 124 (timeout)

# After fix: exits immediately
timeout 10 node bin/cli.js memory stats   # exit 0

Unit tests: 32 passed, 1 skipped (unchanged from baseline).

🤖 Generated with claude-flow

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.

memory init hangs after completion — process does not exit (ONNX worker threads not terminated)

2 participants