When implementing error handling, ensure (1) failures are validated early or handled explicitly, (2) optional steps degrade gracefully, (3) cancellation/abort prevents unnecessary work, and (4) cleanup never masks the true result.
Practical rules: 1) Fail fast on invalid configuration
2) Use safe fallbacks when detection/lookup can be uncertain
3) Treat optional capabilities as non-fatal
4) Respect abort/cancellation as an explicit control flow
5) Preserve error precedence during cleanup
finally block.6) Don’t emit misleading secondary errors/warnings
Example (pattern):
async function runWithPreflightAndCleanup(opts: {
abortSignal?: AbortSignal;
envOverrides?: Record<string, string>;
}) {
if (opts.abortSignal?.aborted) {
throw Object.assign(new Error('Aborted'), { name: 'AbortError' });
}
const prev = new Map<string, string | undefined>();
let primaryError: unknown;
try {
// Preflight validation (fail fast)
const binary = opts.envOverrides?.COPILOT_BIN_PATH;
if (binary !== undefined && binary.trim() === '') {
throw new Error('COPILOT_BIN_PATH is set but blank');
}
// Optional step (non-fatal)
try {
await maybeOptionalStep();
} catch (err) {
logger.warn({ err }, 'optional_step_failed');
}
// Primary work
return await doPrimaryWork();
} catch (err) {
primaryError = err;
throw err;
} finally {
// Cleanup must not mask primary outcome
try {
if (opts.envOverrides) {
for (const [k, v] of Object.entries(opts.envOverrides)) {
if (prev.has(k)) {
const old = prev.get(k);
if (old === undefined) Reflect.deleteProperty(process.env, k);
else process.env[k] = old;
}
}
}
} catch (cleanupErr) {
logger.warn({ cleanupErr }, 'cleanup_failed');
// never replace/override primaryError
}
}
}
Enter the URL of a public GitHub repository