refactor: replace indiscriminate try/catch with guarded Result helpers (#2477)

Add tryCatchIf/asyncTryCatchIf with error predicates (isFileError,
isNetworkError, isOperationalError) so operational errors are handled
explicitly while programming bugs (TypeError, ReferenceError) propagate
and crash visibly instead of being silently swallowed.

Transforms ~40 try/catch blocks across 14 files:
- File I/O (manifest cache, config loading, history) → tryCatchIf(isFileError)
- Network/fetch (API calls, version checks, OAuth) → asyncTryCatchIf(isNetworkError)
- SSH/subprocess (agent setup, tunnel) → asyncTryCatchIf(isOperationalError)
- API retry loops (DO, Hetzner) → guard retries with isNetworkError

Intentionally keeps ~85 try/catch blocks as-is (cleanup/finally, retry
loops, user-facing error handlers, catch-classify-rethrow patterns).

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
A 2026-03-10 18:55:07 -07:00 committed by GitHub
parent 7444c3bbc6
commit 3fd17e3d1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 645 additions and 163 deletions

View file

@ -11,6 +11,7 @@ import { offerGithubAuth, wrapSshCall } from "./agent-setup";
import { tryTarballInstall } from "./agent-tarball";
import { generateEnvConfig } from "./agents";
import { getOrPromptApiKey } from "./oauth";
import { asyncTryCatchIf, isOperationalError } from "./result.js";
import { startSshTunnel } from "./ssh";
import { ensureSshKeys, getSshKeyOpts } from "./ssh-keys";
import { getErrorMessage } from "./type-guards";
@ -89,6 +90,7 @@ export async function runOrchestration(
await cloud.authenticate();
// 1b. Pre-flight account readiness check (billing, email verification, etc.)
// Uses try/catch (not guarded) because hooks can throw ANY provider-specific error.
if (cloud.checkAccountReady) {
try {
await cloud.checkAccountReady();
@ -103,6 +105,7 @@ export async function runOrchestration(
const apiKey = await getOrPromptApiKey(agentName, cloud.cloudName);
// 3. Pre-provision hooks (e.g., GitHub auth prompt — non-fatal)
// Uses try/catch (not guarded) because hooks can throw ANY provider-specific error.
if (agent.preProvision) {
try {
await agent.preProvision();
@ -214,7 +217,7 @@ export async function runOrchestration(
if (agent.tunnel) {
if (cloud.getConnectionInfo) {
// SSH-based cloud: tunnel the remote port to localhost
try {
const tunnelResult = await asyncTryCatchIf(isOperationalError, async () => {
const conn = cloud.getConnectionInfo();
const keys = await ensureSshKeys();
tunnelHandle = await startSshTunnel({
@ -229,7 +232,8 @@ export async function runOrchestration(
openBrowser(url);
}
}
} catch {
});
if (!tunnelResult.ok) {
logWarn("Web dashboard tunnel failed — use the TUI instead");
}
} else if (cloud.cloudName === "local") {