fix: graceful degradation when native binding fails to load#324
fix: graceful degradation when native binding fails to load#324anandgupta42 wants to merge 1 commit intomainfrom
Conversation
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
📝 WalkthroughWalkthroughThe 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
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
packages/opencode/src/altimate/native/dispatcher.tspackages/opencode/src/altimate/native/index.tspackages/opencode/src/altimate/tools/sql-classify.ts
| await import("./sql/register") | ||
| await import("./connections/register") | ||
| await import("./schema/register") | ||
| await import("./finops/register") | ||
| await import("./dbt/register") | ||
| await import("./local/register") |
There was a problem hiding this comment.
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.
| export function classifyAndCheck(sql: string): { queryType: "read" | "write"; blocked: boolean } { | ||
| const core = getCore() | ||
| if (!core) return { queryType: "write", blocked: false } |
There was a problem hiding this comment.
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.
| 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.
✅ Tests — All PassedTypeScript — passedcc @anandgupta42 |
What does this PR do?
On systems with older GLIBC (e.g. Ubuntu 20.04 with GLIBC 2.31),
@altimateai/altimate-corerequires 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 bindingstack trace.After: CLI starts normally. SQL analysis tools are unavailable, but everything else works. User sees a clear warning.
Type of change
Issue for this PR
Closes #310
How did you verify your code works?
getCore()returns null,classify()returns"write"(safe default) andclassifyAndCheck()returns{ queryType: "write", blocked: false }Files changed
src/altimate/tools/sql-classify.tsaltimate-coreon first call instead of eagerrequire(). Falls back to"write"when unavailablesrc/altimate/native/index.tsaltimate-coreimport failure separately so other handlers (connections, schema, finops, dbt) still registersrc/altimate/native/dispatcher.tsChecklist
🤖 Generated with Claude Code
Summary by CodeRabbit