Skip to content

fix(embeddings): retry install with --ignore-scripts on native build failure#249

Open
efenocchi wants to merge 3 commits into
mainfrom
feat/optional-embed-deps
Open

fix(embeddings): retry install with --ignore-scripts on native build failure#249
efenocchi wants to merge 3 commits into
mainfrom
feat/optional-embed-deps

Conversation

@efenocchi

@efenocchi efenocchi commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • On Node v26 macOS, sharp has no prebuilt binary and fails to build from source without node-gyp, causing hivemind embeddings install to abort
  • Wraps the npm install in ensureSharedDeps() in a try/catch: on failure, retries with --ignore-scripts to skip sharp's build
  • After the --ignore-scripts retry, explicitly re-runs onnxruntime-node's postinstall script so its prebuilt binary is still downloaded
  • sharp is image-only in @huggingface/transformers; text embeddings are unaffected

Related

Follows up on #248 (moved @huggingface/transformers to optionalDependencies).

Test plan

  • hivemind embeddings install succeeds on Node v26 macOS without node-gyp installed
  • Embeddings generate vectors normally after install (onnxruntime binary present)
  • Normal install path (Node 22/24) still works unchanged (try block succeeds, catch never runs)

Summary by CodeRabbit

  • Bug Fixes

    • Improved embedding dependency installation with a robust retry fallback, clearer warnings when optional native components are unavailable, and ensured runtime binaries are invoked when present.
  • Chores

    • Included embedding assets in the package publish list so related files are bundled.
  • Tests

    • Added tests covering install, retry, fallback, and warning behaviors for embedding dependency setup.

…failure

On Node v26 macOS, sharp has no prebuilt binary and fails to build from
source without node-gyp. The first npm install attempt is kept as-is so
platforms with prebuilt binaries work normally. On failure we retry with
--ignore-scripts (sharp is image-only; text embeddings are unaffected),
then explicitly re-run onnxruntime-node's postinstall so its binary is
still downloaded.
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

ensureSharedDeps() now calls _installWithFallback(), which runs npm install, retries with --ignore-scripts on failure (emitting warnings about sharp/image native support), and conditionally re-executes onnxruntime-node's install script if present.

Changes

Embedding dependency installation resilience

Layer / File(s) Summary
_installWithFallback implementation and invocation
src/cli/embeddings.ts, package.json
Adds _installWithFallback() implementing npm install, retry with --ignore-scripts, conditional onnxruntime-node install script execution, and updates ensureSharedDeps() to call it; includes embeddings in package files.
Unit tests for install fallback
tests/cli/cli-embeddings.test.ts
Adds Vitest suite validating happy path, retry-with---ignore-scripts, conditional execution of onnxruntime-node's install script, warning-on-onnx-failure, hard-fail retry failure, and skipping when onnxruntime script absent.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • kaghni

Poem

🐰 I stitched a helper, npm to mend,
Tried once, then skipped scripts to the end,
If ONNX hides its install call,
I nudge it back — or just warn all.
Embeddings stand ready — hop and send!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the problem, solution, and impact clearly. However, the test plan checkboxes remain unchecked and no version bump is mentioned, which are required by the template. Complete the test plan checkboxes to indicate test status and add a version bump in package.json (patch version for bug fix) as required by the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary change: retrying npm install with --ignore-scripts when native builds fail, which directly addresses the embeddings installation issue on Node v26 macOS.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/optional-embed-deps

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Scope: files changed in this PR. Enforced threshold: 90% per metric (per file via vitest.config.ts).

Status Category Percentage Covered / Total
🔴 Lines 52.78% (🎯 90%) 76 / 144
🔴 Statements 53.05% (🎯 90%) 87 / 164
🔴 Functions 76.47% (🎯 90%) 13 / 17
🔴 Branches 52.70% (🎯 90%) 39 / 74
File Coverage — 1 file changed
File Stmts Branches Functions Lines
src/cli/embeddings.ts 🔴 53.0% 🔴 52.7% 🔴 76.5% 🔴 52.8%

Generated for commit bdc8c24.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/cli/embeddings.ts (2)

128-134: ⚡ Quick win

Consider adding error handling for the ONNX runtime install script.

If the ONNX runtime's install.js script fails to execute, the user won't get a clear indication of the failure, and text embeddings will silently break (since ONNX is required for the transformers library to function).

Wrapping this in try/catch with a warning would help users understand when the fallback path has succeeded vs. when it has partially failed.

🛡️ Suggested error handling
       const onnxScript = join(SHARED_NODE_MODULES, "onnxruntime-node", "script", "install.js");
       if (existsSync(onnxScript)) {
-        execFileSync("node", [onnxScript], {
-          cwd: join(SHARED_NODE_MODULES, "onnxruntime-node"),
-          stdio: "inherit",
-        });
+        try {
+          execFileSync("node", [onnxScript], {
+            cwd: join(SHARED_NODE_MODULES, "onnxruntime-node"),
+            stdio: "inherit",
+          });
+        } catch (onnxErr) {
+          warn(`  Embeddings     onnxruntime-node install script failed — embeddings may not work`);
+          throw onnxErr;
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/embeddings.ts` around lines 128 - 134, Wrap the execFileSync call
that runs the ONNX install script (the block using onnxScript, existsSync,
execFileSync and SHARED_NODE_MODULES) in a try/catch; if execFileSync throws,
catch the error and log a clear warning/error (e.g., using processLogger or
console.warn/error) stating the ONNX install failed and include the error
message so users know embeddings may not work, but continue execution (don’t
rethrow) to allow fallback behavior.

119-135: ⚖️ Poor tradeoff

The catch block is too broad and may produce misleading diagnostics.

The catch block assumes any install failure is due to native build issues (sharp requiring node-gyp), but failures can also occur due to network issues, disk space, permissions, or npm registry problems. In those cases, retrying with --ignore-scripts won't help, and the warning "sharp image support unavailable" will be misleading.

Consider either:

  • Checking the error message/code to distinguish native build failures from other errors
  • Making the warning messages more generic (e.g., "install failed; retrying without build scripts")
  • Logging the original error to help users diagnose non-native issues
💡 Example: Log original error for debugging
-    } catch {
+    } catch (err) {
       // sharp has no prebuilt binary for some Node versions (e.g. Node v26 on macOS)
       // and requires node-gyp to build from source. Retry without scripts — sharp is
       // only used for image processing; text embeddings are unaffected.
-      warn(`  Embeddings     native build failed; retrying without install scripts`);
+      warn(`  Embeddings     install failed (${err instanceof Error ? err.message : String(err)}); retrying without install scripts`);
       warn(`  Embeddings     (sharp image support unavailable — text embeddings unaffected)`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/embeddings.ts` around lines 119 - 135, Change the bare catch to catch
the error (e.g., catch (err)) and log the original error via warn so users can
see the underlying cause; then inspect err.message/err.code for native-build
indicators (match keywords like "sharp", "node-gyp", "prebuild", "gyp") and only
perform the --ignore-scripts retry and the sharp-specific warnings/onnx
postinstall when those indicators are present; otherwise emit a generic failure
warning (e.g., "install failed; not retrying without build scripts") and rethrow
or exit to avoid masking non-native issues (update the block around
execFileSync/npm retry, the onnxScript logic, and the warn calls to reference
err).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/embeddings.ts`:
- Line 125: The retry execFileSync call that runs "npm" with [...npmArgs,
"--ignore-scripts"] in embeddings.ts needs a try/catch so failures produce a
clear error message instead of an unhandled exception; wrap the
execFileSync(...) call in try { ... } catch (err) { console.error or
processLogger.error with a descriptive message including err.message (and the
npmArgs/SHARED_DIR context), then call process.exit(1) } to ensure graceful
failure handling.

---

Nitpick comments:
In `@src/cli/embeddings.ts`:
- Around line 128-134: Wrap the execFileSync call that runs the ONNX install
script (the block using onnxScript, existsSync, execFileSync and
SHARED_NODE_MODULES) in a try/catch; if execFileSync throws, catch the error and
log a clear warning/error (e.g., using processLogger or console.warn/error)
stating the ONNX install failed and include the error message so users know
embeddings may not work, but continue execution (don’t rethrow) to allow
fallback behavior.
- Around line 119-135: Change the bare catch to catch the error (e.g., catch
(err)) and log the original error via warn so users can see the underlying
cause; then inspect err.message/err.code for native-build indicators (match
keywords like "sharp", "node-gyp", "prebuild", "gyp") and only perform the
--ignore-scripts retry and the sharp-specific warnings/onnx postinstall when
those indicators are present; otherwise emit a generic failure warning (e.g.,
"install failed; not retrying without build scripts") and rethrow or exit to
avoid masking non-native issues (update the block around execFileSync/npm retry,
the onnxScript logic, and the warn calls to reference err).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 123519d0-04f8-4455-9a31-8a1d3406da08

📥 Commits

Reviewing files that changed from the base of the PR and between 02f73be and d3b52b8.

📒 Files selected for processing (1)
  • src/cli/embeddings.ts

Comment thread src/cli/embeddings.ts Outdated
efenocchi added 2 commits June 9, 2026 02:18
embeddings/embed-daemon.js is built by esbuild but was missing from the
files array, so it was never published to npm. hivemind embeddings install
copies this file to ~/.hivemind/embed-deps/embed-daemon.js — without it
the daemon is absent and no embeddings are generated.
…Rabbit fixes

Extract install-with-fallback logic into exported _installWithFallback so
it can be tested with an injected exec function. Tests cover all 5 branches:
happy path, retry on native build failure, onnxruntime script re-run,
onnxruntime script failure (warns, no throw), and hard-fail (throws).

Also applies CodeRabbit feedback: log original error in catch, wrap retry
execFileSync in its own try/catch with a descriptive throw, and wrap the
onnxruntime script execution in try/catch with a user-visible warning.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/cli/cli-embeddings.test.ts (1)

353-355: ⚡ Quick win

Use exact path/message assertions in fallback tests.

These checks currently accept generic substrings, so they can pass on incorrect values. Please assert exact expected args/path/message for stronger contract coverage.

Suggested test-tightening diff
@@
   it("retry path: first call fails → retries with --ignore-scripts", () => {
@@
     _installWithFallback("/fake/shared", "/fake/nm", exec);
     expect(exec).toHaveBeenCalledTimes(2);
-    const [, secondArgs] = exec.mock.calls[1] as [string, string[]];
-    expect(secondArgs).toContain("--ignore-scripts");
+    expect(exec).toHaveBeenNthCalledWith(
+      2,
+      "npm",
+      ["install", "--omit=dev", "--no-package-lock", "--no-audit", "--no-fund", "--ignore-scripts"],
+      expect.objectContaining({ cwd: "/fake/shared" }),
+    );
   });
@@
     // npm install (fail) + npm install --ignore-scripts + node onnxScript
     expect(exec).toHaveBeenCalledTimes(3);
-    const [thirdCmd, thirdArgs] = exec.mock.calls[2] as [string, string[]];
-    expect(thirdCmd).toBe("node");
-    expect(thirdArgs[0]).toContain("onnxruntime-node");
-    expect(thirdArgs[0]).toContain("install.js");
+    const expectedOnnxScript = join(sharedNm, "onnxruntime-node", "script", "install.js");
+    expect(exec).toHaveBeenNthCalledWith(
+      3,
+      "node",
+      [expectedOnnxScript],
+      expect.objectContaining({ cwd: join(sharedNm, "onnxruntime-node") }),
+    );
   });
@@
   it("hard-fail path: throws when retry with --ignore-scripts also fails", () => {
     const exec = vi.fn(() => { throw new Error("disk full"); }) as any;
     expect(() => _installWithFallback("/fake/shared", "/fake/nm", exec))
-      .toThrow("Embedding deps install failed even without scripts");
+      .toThrowError(/^Embedding deps install failed even without scripts: disk full$/);
   });

As per coding guidelines, tests/**: Prefer asserting on specific values (paths, messages) over generic substrings.

Also applies to: 372-375, 394-395

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/cli/cli-embeddings.test.ts` around lines 353 - 355, Replace loose
substring assertions like expect(secondArgs).toContain("--ignore-scripts") with
exact-value assertions that compare the entire args array or the exact argument
string; locate the uses around exec.mock.calls (e.g., the destructuring into
secondArgs) and change them to assert equality (e.g.,
expect(secondArgs).toEqual([...expected exact args...]) or
expect(secondArgs).toContainEqual("--ignore-scripts") if matching a single exact
element), and apply the same tightening for the other occurrences mentioned
(lines with expects at 372-375 and 394-395) so tests verify exact paths/messages
instead of generic substrings.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/cli/cli-embeddings.test.ts`:
- Around line 353-355: Replace loose substring assertions like
expect(secondArgs).toContain("--ignore-scripts") with exact-value assertions
that compare the entire args array or the exact argument string; locate the uses
around exec.mock.calls (e.g., the destructuring into secondArgs) and change them
to assert equality (e.g., expect(secondArgs).toEqual([...expected exact
args...]) or expect(secondArgs).toContainEqual("--ignore-scripts") if matching a
single exact element), and apply the same tightening for the other occurrences
mentioned (lines with expects at 372-375 and 394-395) so tests verify exact
paths/messages instead of generic substrings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fc6dcccd-d871-4899-911e-9440eb6738e2

📥 Commits

Reviewing files that changed from the base of the PR and between d3b52b8 and 1bfe897.

📒 Files selected for processing (3)
  • package.json
  • src/cli/embeddings.ts
  • tests/cli/cli-embeddings.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cli/embeddings.ts

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.

1 participant