Skip to content

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

Open
anandgupta42 wants to merge 1 commit intomainfrom
fix/graceful-native-binding-failure
Open

fix: graceful degradation when native binding fails to load#324
anandgupta42 wants to merge 1 commit 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
    • Enhanced error handling for unavailable native dependencies. The system now gracefully continues with safe fallback behaviors instead of failing completely.
    • Improved stability and reliability in SQL classification and handler registration when native bindings are inaccessible.
    • Fallback mechanisms ensure continued operation with default safe values when optional native features are unavailable.

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

📝 Walkthrough

Walkthrough

The changes implement graceful error handling for native binding failures across three modules. When the application encounters errors related to missing GLIBC versions or unavailable native bindings (ERR_DLOPEN_FAILED), the errors are now caught and suppressed, allowing the application to continue with fallback behavior rather than crashing on startup.

Changes

Cohort / File(s) Summary
Native Handler Registration
packages/opencode/src/altimate/native/dispatcher.ts, packages/opencode/src/altimate/native/index.ts
Added try/catch blocks around lazy handler registration and dynamic module imports. Native binding, GLIBC, and ERR_DLOPEN_FAILED errors are suppressed to allow execution to continue; other errors are rethrown.
SQL Classification Tool
packages/opencode/src/altimate/tools/sql-classify.ts
Converted eager module import to lazy loader (getCore()) with caching. Returns null if native binding fails. classify() and classifyAndCheck() functions now handle unavailable core by returning safe defaults ("write" and { queryType: "write", blocked: false } respectively).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A rabbit hops through failing binds,
With gentle catch blocks, peace she finds,
No crash, no burn, just fallback grace,
Graceful errors win the race! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: graceful degradation when native binding fails to load, which is the central objective of the PR.
Description check ✅ Passed The description includes What/Why, Type of change, linked issue, verification steps, files changed, and checklist items. However, Test Plan and CHANGELOG sections are missing from the template structure.
Linked Issues check ✅ Passed The PR fully addresses issue #310: prevents startup crash by catching native binding errors, allows CLI to start normally, and provides warnings instead of unhandled stack traces.
Out of Scope Changes check ✅ Passed All changes are directly scoped to gracefully handling native binding failures in the three identified files with no unrelated modifications present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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
📝 Coding Plan
  • Generate coding plan for human review comments

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 23 to 28
await import("./sql/register")
await import("./connections/register")
await import("./schema/register")
await import("./finops/register")
await import("./dbt/register")
await import("./local/register")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

sql/register can still abort all handler registration.

Line 23 is unguarded. If ./sql/register fails (it imports @altimateai/altimate-core directly), execution never reaches Line 24-28, so non-SQL handlers are not registered.

🔧 Proposed fix
-  await import("./sql/register")
   await import("./connections/register")
   await import("./schema/register")
   await import("./finops/register")
   await import("./dbt/register")
   await import("./local/register")
+  try {
+    await import("./sql/register")
+  } catch (e: any) {
+    const msg = String(e?.message || e)
+    if (!(msg.includes("native binding") || msg.includes("GLIBC") || msg.includes("ERR_DLOPEN_FAILED"))) {
+      throw e
+    }
+  }
🤖 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 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.

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.

@dev-punia-altimate
Copy link

✅ Tests — All Passed

TypeScript — passed

cc @anandgupta42
Tested at 5c84bf70 | 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