Skip to content

fix: graceful degradation when native binding fails to load#324

Open
anandgupta42 wants to merge 3 commits intomainfrom
fix/graceful-native-binding-failure
Open

fix: graceful degradation when native binding fails to load#324
anandgupta42 wants to merge 3 commits intomainfrom
fix/graceful-native-binding-failure

Conversation

@anandgupta42
Copy link
Contributor

@anandgupta42 anandgupta42 commented Mar 20, 2026

What does this PR do?

On systems with older GLIBC (e.g. Ubuntu 20.04 with GLIBC 2.31), @altimateai/altimate-core requires GLIBC 2.35 and crashes with an unhandled stack trace on startup, blocking all CLI usage. This PR makes the CLI degrade gracefully instead of crashing.

Before: Any CLI command crashes with Cannot find native binding stack trace.
After: CLI starts normally. SQL analysis tools are unavailable, but everything else works. User sees a clear warning.

Type of change

  • Bug fix (non-breaking change that fixes an issue)

Issue for this PR

Closes #310

How did you verify your code works?

  • sql-classify tests: 45/45 pass (lazy loading works, native binding loads correctly when available)
  • Branding tests: 280/280 pass
  • Typecheck passes
  • Verified that when getCore() returns null, classify() returns "write" (safe default) and classifyAndCheck() returns { queryType: "write", blocked: false }

Files changed

File Change
src/altimate/tools/sql-classify.ts Lazy-load altimate-core on first call instead of eager require(). Falls back to "write" when unavailable
src/altimate/native/index.ts Catch altimate-core import failure separately so other handlers (connections, schema, finops, dbt) still register
src/altimate/native/dispatcher.ts Catch registration hook failures from native binding errors, log clear warning, continue

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • New and existing tests pass locally with my changes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Graceful handling when optional native bindings fail: registration logs a warning for known native-binding failures and continues without crashing.
    • Native-dependent components are lazy-loaded with safe fallbacks to preserve runtime operation.
  • Bug Fixes

    • SQL classification returns a safe default when native features are unavailable, preventing runtime errors.
  • Tests

    • Added integrity tests to verify graceful native-binding degradation and availability of safer modules.

On systems with older GLIBC (e.g. Ubuntu 20.04 with GLIBC 2.31),
`@altimateai/altimate-core` napi-rs binary requires GLIBC 2.35 and crashes
with an unhandled stack trace on startup, blocking all CLI usage.

Three changes to degrade gracefully instead of crashing:

1. `sql-classify.ts`: lazy-load `altimate-core` on first call instead of
   eager `require()` at import time. Falls back to "write" (safe default)
   when native binding is unavailable.

2. `native/index.ts`: catch `altimate-core` import failure separately so
   other handler modules (connections, schema, finops, dbt) still register.

3. `dispatcher.ts`: catch registration hook failures caused by native
   binding errors, log a clear warning, and continue. Non-native-binding
   errors still throw.

Result: CLI starts normally. SQL analysis tools (validate, lint, transpile,
lineage) are unavailable, but warehouse connections, schema indexing, dbt
integration, and all other features work.

Closes #310
Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review.

@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c98e88c1-1f3c-40d6-af81-40e942640b06

📥 Commits

Reviewing files that changed from the base of the PR and between 209a0d2 and 35f913e.

📒 Files selected for processing (1)
  • packages/opencode/src/altimate/native/dispatcher.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/opencode/src/altimate/native/dispatcher.ts

📝 Walkthrough

Walkthrough

Adds fault-tolerant handling around native binding loads and lazy native registration: dynamic imports and lazy registration are wrapped in try/catch that classify and suppress native-binding-related errors (e.g., native binding, GLIBC, ERR_DLOPEN_FAILED) while rethrowing other errors, and provides safe fallbacks when the native core is unavailable.

Changes

Cohort / File(s) Summary
Native dispatcher
packages/opencode/src/altimate/native/dispatcher.ts
Wraps lazy handler registration (_ensureRegistered) in try/catch; logs and suppresses errors whose messages match native-binding/GLIBC/ERR_DLOPEN_FAILED (includes truncated cause), rethrows other registration errors.
Native index / registration hook
packages/opencode/src/altimate/native/index.ts
Adds isNativeBindingError helper; wraps dynamic import("./altimate-core") and core-dependent module imports in try/catch loops, swallowing only classified native-binding failures and rethrowing other errors; leaves non-core modules as direct imports.
SQL classification tool
packages/opencode/src/altimate/tools/sql-classify.ts
Replaces eager require with cached lazy getCore() that returns null on native-binding failures; classify() and classifyAndCheck() return safe defaults ("write" / {queryType:"write", blocked:false}) when core is unavailable.
Tests — branding/build integrity
packages/opencode/test/branding/build-integrity.test.ts
Replaces an install-script test with a “Graceful Native Binding Degradation” suite asserting presence of isNativeBindingError, a try/catch import pattern for altimate-core, that certain core-dependent modules are not top-level bare imports, and that safe modules (connections/register, finops/register) remain present.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I’m a rabbit, light and spry,
I catch the errors as they fly,
If native bindings fail to load,
I nibble warnings — spare the road,
Onward I hop, no crash, just try 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main purpose of the PR: implementing graceful degradation when native binding fails to load.
Description check ✅ Passed The PR description follows the repository template with all required sections: Summary (what changed and why), Test Plan (verification methods), and Checklist items completed.
Linked Issues check ✅ Passed The PR fully addresses issue #310 requirements: CLI no longer crashes on startup with native binding errors, degrades gracefully with fallback behavior (classify returns 'write', classifyAndCheck returns safe defaults), and logs a clear warning message.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing native binding failure handling. Three files modified are focused: dispatcher catches registration failures, index.ts handles module import failures, and sql-classify implements graceful fallbacks. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 fix/graceful-native-binding-failure

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/opencode/src/altimate/native/dispatcher.ts (1)

49-50: Consolidate native-load error detection into a shared helper.

The same message-matching logic is duplicated here and in native/index.ts; centralizing it reduces drift and keeps graceful-degradation behavior consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/altimate/native/dispatcher.ts` around lines 49 - 50,
Extract the duplicated message-matching logic (msg.includes("native binding") ||
msg.includes("GLIBC") || msg.includes("ERR_DLOPEN_FAILED")) into a single
exported helper like isNativeLoadError(message: string): boolean in the native
module (e.g., export from native/index.ts or a small native/errors.ts), then
import and use that helper from dispatcher.ts (replace the inline check in the
block containing console.error) and from the other location in native/index.ts
so both places call isNativeLoadError(msg) instead of duplicating the string
checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/altimate/native/index.ts`:
- Around line 23-28: The unguarded top-level await of "./sql/register" can throw
and stop registration of the other handlers; wrap each dynamic import (at least
"./sql/register", "./connections/register", "./schema/register",
"./finops/register", "./dbt/register", "./local/register") so a failure in one
doesn’t abort the rest — e.g., perform the imports with individual try/catch
blocks or use Promise.allSettled on the array of import() promises, log or
surface errors (including the error from the sql/register import which pulls in
`@altimateai/altimate-core`) and continue registering remaining modules.

In `@packages/opencode/src/altimate/tools/sql-classify.ts`:
- Around line 58-60: The fallback in classifyAndCheck currently returns {
queryType: "write", blocked: false } when getCore() is null, which allows
hard-deny SQL to pass; change the fallback to conservatively block by returning
{ queryType: "write", blocked: true } (or otherwise ensure blocked is true) so
that when native parsing via getCore() is unavailable, classifyAndCheck treats
statements as writes and enforces hard-deny blocking.

---

Nitpick comments:
In `@packages/opencode/src/altimate/native/dispatcher.ts`:
- Around line 49-50: Extract the duplicated message-matching logic
(msg.includes("native binding") || msg.includes("GLIBC") ||
msg.includes("ERR_DLOPEN_FAILED")) into a single exported helper like
isNativeLoadError(message: string): boolean in the native module (e.g., export
from native/index.ts or a small native/errors.ts), then import and use that
helper from dispatcher.ts (replace the inline check in the block containing
console.error) and from the other location in native/index.ts so both places
call isNativeLoadError(msg) instead of duplicating the string checks.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9a4c6828-02b1-45a6-af14-6d0f59bcaafa

📥 Commits

Reviewing files that changed from the base of the PR and between df24e73 and 5c84bf7.

📒 Files selected for processing (3)
  • packages/opencode/src/altimate/native/dispatcher.ts
  • packages/opencode/src/altimate/native/index.ts
  • packages/opencode/src/altimate/tools/sql-classify.ts

Comment on lines 58 to +60
export function classifyAndCheck(sql: string): { queryType: "read" | "write"; blocked: boolean } {
const core = getCore()
if (!core) return { queryType: "write", blocked: false }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hard-deny safety is bypassed when native core is unavailable.

On Line 60, fallback returns blocked: false, which allows hard-deny statements to proceed through the write-permission path instead of being unconditionally blocked. Keep a conservative hard-deny fallback even without native parsing.

🔧 Proposed fix
 const HARD_DENY_TYPES = new Set(["DROP DATABASE", "DROP SCHEMA", "TRUNCATE", "TRUNCATE TABLE"])
+const HARD_DENY_FALLBACK_REGEX = /\bDROP\s+(DATABASE|SCHEMA)\b|\bTRUNCATE(?:\s+TABLE)?\b/i
@@
 export function classifyAndCheck(sql: string): { queryType: "read" | "write"; blocked: boolean } {
   const core = getCore()
-  if (!core) return { queryType: "write", blocked: false }
+  if (!core) {
+    return { queryType: "write", blocked: HARD_DENY_FALLBACK_REGEX.test(sql) }
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function classifyAndCheck(sql: string): { queryType: "read" | "write"; blocked: boolean } {
const core = getCore()
if (!core) return { queryType: "write", blocked: false }
export function classifyAndCheck(sql: string): { queryType: "read" | "write"; blocked: boolean } {
const core = getCore()
if (!core) {
return { queryType: "write", blocked: HARD_DENY_FALLBACK_REGEX.test(sql) }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/altimate/tools/sql-classify.ts` around lines 58 - 60,
The fallback in classifyAndCheck currently returns { queryType: "write",
blocked: false } when getCore() is null, which allows hard-deny SQL to pass;
change the fallback to conservatively block by returning { queryType: "write",
blocked: true } (or otherwise ensure blocked is true) so that when native
parsing via getCore() is unavailable, classifyAndCheck treats statements as
writes and enforces hard-deny blocking.

…ation

`sql/register`, `schema/register`, `dbt/register`, and `local/register`
all transitively import `@altimateai/altimate-core`. Previously only the
direct `altimate-core` import was wrapped — if any register module failed
due to GLIBC mismatch, the remaining modules wouldn't load either.

Now each core-dependent module is individually wrapped so a native binding
failure in one doesn't prevent the others from registering. Modules that
don't depend on core (`connections/register`, `finops/register`) load
unconditionally.

Adds CI guard tests to `build-integrity.test.ts` that verify:
- `isNativeBindingError` helper exists
- `altimate-core` import is wrapped in try/catch
- All 4 core-dependent modules are NOT bare `await import()` calls
- Both safe modules are still referenced

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/opencode/src/altimate/native/index.ts (1)

9-12: Consider centralizing native-binding error matching.

isNativeBindingError appears logically shared with dispatcher behavior. Moving this matcher to one shared helper will prevent drift when patterns evolve.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/altimate/native/index.ts` around lines 9 - 12, The
native-binding error matcher in isNativeBindingError should be moved to a shared
helper to avoid duplicated logic; extract the logic (currently in
isNativeBindingError) into a single exported function (e.g.,
matchesNativeBindingError or isNativeBindingError in a new/shared utils module),
replace the local implementation in
packages/opencode/src/altimate/native/index.ts with an import from that shared
helper, update the dispatcher code to import and use the same helper (remove its
own matching logic), and run/adjust any tests or usages to reference the shared
symbol to ensure no duplicated patterns remain.
packages/opencode/test/branding/build-integrity.test.ts (1)

366-366: Consider escaping mod in the RegExp to future-proof against special characters.

While the current module list contains only literal-safe names (sql/register, schema/register, dbt/register, local/register), escaping the interpolated value guards against regex fragility if module names change to include metacharacters.

Suggested hardening
+  const escapeRegExp = (value: string) =>
+    value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
+
   for (const mod of coreDepModules) {
     test(`${mod} import is wrapped in try/catch (not bare await)`, () => {
       // Each core-dependent module should appear inside the coreDependent array or
       // a try/catch, NOT as a bare `await import("./module")`.
-      const barePattern = new RegExp(`^\\s*await import\\(["']\\.\\/${mod}["']\\)`, "m")
+      const barePattern = new RegExp(
+        `^\\s*await import\\(["']\\.\\/${escapeRegExp(mod)}["']\\)`,
+        "m",
+      )
       expect(nativeIndex).not.toMatch(barePattern)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/branding/build-integrity.test.ts` at line 366, The
RegExp construction for barePattern uses the interpolated variable mod directly
which can break if mod contains regex metacharacters; update the code that
builds barePattern (the RegExp(...) call that assigns barePattern) to escape mod
before interpolation (e.g., implement or call an escapeRegExp utility to
sanitize the mod string) and then use the escaped value in new RegExp so the
pattern reliably matches literal module names.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/opencode/src/altimate/native/index.ts`:
- Around line 9-12: The native-binding error matcher in isNativeBindingError
should be moved to a shared helper to avoid duplicated logic; extract the logic
(currently in isNativeBindingError) into a single exported function (e.g.,
matchesNativeBindingError or isNativeBindingError in a new/shared utils module),
replace the local implementation in
packages/opencode/src/altimate/native/index.ts with an import from that shared
helper, update the dispatcher code to import and use the same helper (remove its
own matching logic), and run/adjust any tests or usages to reference the shared
symbol to ensure no duplicated patterns remain.

In `@packages/opencode/test/branding/build-integrity.test.ts`:
- Line 366: The RegExp construction for barePattern uses the interpolated
variable mod directly which can break if mod contains regex metacharacters;
update the code that builds barePattern (the RegExp(...) call that assigns
barePattern) to escape mod before interpolation (e.g., implement or call an
escapeRegExp utility to sanitize the mod string) and then use the escaped value
in new RegExp so the pattern reliably matches literal module names.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2deec7a3-1738-4fd8-a032-2908125d2fbb

📥 Commits

Reviewing files that changed from the base of the PR and between 5c84bf7 and 209a0d2.

📒 Files selected for processing (2)
  • packages/opencode/src/altimate/native/index.ts
  • packages/opencode/test/branding/build-integrity.test.ts

Users seeing the GLIBC mismatch warning now get actionable fix steps:
which distro versions have GLIBC >= 2.35, Docker workaround, and how
to check their current GLIBC version.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dev-punia-altimate
Copy link

✅ Tests — All Passed

TypeScript — passed

cc @anandgupta42
Tested at 35f913e8 | Run log | Powered by QA Autopilot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] GLIBC_2.35 not found or missing native binding

2 participants