fix: prevent false 'Failed to save connection' error when adding providers (#8912)

Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates green: static + changed tests + vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-213228-suite.log
This commit is contained in:
Zius
2026-08-06 06:11:33 +05:30
committed by GitHub
parent b4d7e86521
commit a5e0a96f8a
6 changed files with 65 additions and 18 deletions

View File

@@ -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));

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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() };
}

View File

@@ -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");
}
},

View File

@@ -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) {