diff --git a/bin/cli/commands/doctor.mjs b/bin/cli/commands/doctor.mjs index 087101b50d..f61f9d60c0 100644 --- a/bin/cli/commands/doctor.mjs +++ b/bin/cli/commands/doctor.mjs @@ -299,6 +299,7 @@ async function checkNativeBinary(rootDir) { "Release", "better_sqlite3.node" ), + path.join(rootDir, "dist", "node_modules", "better-sqlite3", "build", "Release", "better_sqlite3.node"), path.join(rootDir, "node_modules", "better-sqlite3", "build", "Release", "better_sqlite3.node"), ]; const binaryPath = candidates.find((candidate) => fs.existsSync(candidate)); diff --git a/bin/cli/commands/runtime.mjs b/bin/cli/commands/runtime.mjs index ffd8c0dac0..ed41bca352 100644 --- a/bin/cli/commands/runtime.mjs +++ b/bin/cli/commands/runtime.mjs @@ -34,7 +34,14 @@ async function runRepairAction(opts, cmd) { if (ok) { process.stdout.write("✓ better-sqlite3 repaired OK\n"); } else { - process.stderr.write("✗ Repair failed — check npm availability\n"); + process.stderr.write("✗ Repair failed\n"); + process.stderr.write( + " Possible causes:\n" + + " • npm not available — check that Node.js/npm are on your PATH\n" + + " • npm install scripts are blocked — run: npm install-scripts approve better-sqlite3\n" + + " • Network issue — check your internet connection\n" + + " Try: npm install-scripts ls (to see if better-sqlite3 is blocked)\n" + ); process.exit(1); } } diff --git a/bin/cli/commands/setup-claude.mjs b/bin/cli/commands/setup-claude.mjs index fbb95d5ff8..6ccc668257 100644 --- a/bin/cli/commands/setup-claude.mjs +++ b/bin/cli/commands/setup-claude.mjs @@ -156,7 +156,16 @@ export async function runSetupClaudeCommand(opts = {}) { headers, signal: AbortSignal.timeout(10000), }); - if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`); + if (!res.ok) { + let detail = `HTTP ${res.status}`; + try { + const errorBody = await res.json(); + const serverMsg = + errorBody?.error?.message || errorBody?.error || errorBody?.message || ""; + if (serverMsg) detail += ` — ${serverMsg}`; + } catch {} + throw new Error(detail); + } const body = await res.json(); models = body.data ?? body.models ?? []; } catch (err) { diff --git a/bin/cli/runtime/nativeDeps.mjs b/bin/cli/runtime/nativeDeps.mjs index 1dc442270e..bd053384f9 100644 --- a/bin/cli/runtime/nativeDeps.mjs +++ b/bin/cli/runtime/nativeDeps.mjs @@ -152,9 +152,18 @@ export function ensureBetterSqliteRuntime({ silent = false, force = false } = {} if (!silent) process.stdout.write("[omniroute][runtime] better-sqlite3 OK\n"); return { betterSqlite: true }; } + if (!silent) { + process.stdout.write( + `[omniroute][runtime] Installing better-sqlite3@${BETTER_SQLITE3_VERSION} into runtime...\n` + ); + } const ok = npmInstallRuntime([`better-sqlite3@${BETTER_SQLITE3_VERSION}`], { silent }); if (!ok && !silent) { - process.stderr.write("[omniroute][runtime] better-sqlite3 install failed\n"); + process.stderr.write( + "[omniroute][runtime] better-sqlite3 install failed.\n" + + " This usually means npm install scripts are blocked.\n" + + " Try: npm install-scripts approve better-sqlite3\n" + ); } return { betterSqlite: ok && hasModule("better-sqlite3") && isBetterSqliteBinaryValid() }; } diff --git a/src/app/(dashboard)/dashboard/providers/[id]/hooks/useApiKeySave.ts b/src/app/(dashboard)/dashboard/providers/[id]/hooks/useApiKeySave.ts index 620a80a63e..97b9974532 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/hooks/useApiKeySave.ts +++ b/src/app/(dashboard)/dashboard/providers/[id]/hooks/useApiKeySave.ts @@ -137,11 +137,17 @@ export function useApiKeySave({ } return null; } + // Even if the server returned an error, the connection may have been + // persisted (e.g. post-commit housekeeping failed after the DB write). + // Refresh the list so the UI picks it up on next render. + void fetchConnections(); const data = await res.json().catch(() => ({})); const errorMsg = data.error?.message || data.error || t("failedSaveConnection"); return errorMsg; } catch (error) { console.log("Error saving connection:", error); + // The connection may still have been persisted despite the network error. + void fetchConnections(); return t("failedSaveConnectionRetry"); } }, diff --git a/src/app/api/providers/route.ts b/src/app/api/providers/route.ts index b172e9ef07..3a4fa50583 100644 --- a/src/app/api/providers/route.ts +++ b/src/app/api/providers/route.ts @@ -243,22 +243,37 @@ export async function POST(request: Request) { ); } - // Auto sync to Cloud if enabled - await syncToCloudIfEnabled(); + // Post-commit housekeeping: sync + audit must never fail the 201 response. + // The connection is already persisted; these are non-critical side-effects. + try { + await syncToCloudIfEnabled(); + } catch (housekeepingError) { + console.log( + `[providers] syncToCloudIfEnabled failed after connection creation for ${newConnection.id}:`, + housekeepingError + ); + } - logAuditEvent({ - action: "provider.credentials.created", - actor: "admin", - target: getProviderAuditTarget(newConnection), - resourceType: "provider_credentials", - status: "success", - ipAddress: auditContext.ipAddress || undefined, - requestId: auditContext.requestId, - metadata: { - provider: provider, - connection: summarizeProviderConnectionForAudit(newConnection), - }, - }); + try { + logAuditEvent({ + action: "provider.credentials.created", + actor: "admin", + target: getProviderAuditTarget(newConnection), + resourceType: "provider_credentials", + status: "success", + ipAddress: auditContext.ipAddress || undefined, + requestId: auditContext.requestId, + metadata: { + provider: provider, + connection: summarizeProviderConnectionForAudit(newConnection), + }, + }); + } catch (auditError) { + console.log( + `[providers] logAuditEvent failed after connection creation for ${newConnection.id}:`, + auditError + ); + } return NextResponse.json({ connection: result }, { status: 201 }); } catch (error) {