fix: resolve all 301 error handling anti-patterns across codebase

Systematic cleanup of every error handling anti-pattern detected by the
automated scanner. 289 issues fixed via code changes, 12 approved with
specific technical justifications.

Changes across 90 files:
- GENERIC_CATCH (141): Added instanceof Error type discrimination
- LARGE_TRY_BLOCK (82): Extracted helper methods to narrow try scope to ≤10 lines
- NO_LOGGING_IN_CATCH (65): Added logger/console calls for error visibility
- CATCH_AND_CONTINUE_CRITICAL_PATH (10): Added throw/return or approved overrides
- ERROR_STRING_MATCHING (2): Approved with rationale (no typed error classes)
- ERROR_MESSAGE_GUESSING (1): Replaced chained .includes() with documented pattern array
- PROMISE_CATCH_NO_LOGGING (1): Added logging to .catch() handler

Also fixes a detector bug where nested try/catch inside a catch block
corrupted brace-depth tracking, causing false positives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-04-19 19:57:00 -07:00
parent c9adb1c77b
commit a0dd516cd5
91 changed files with 4846 additions and 3414 deletions
+56 -31
View File
@@ -34,40 +34,38 @@ export class SessionQueueProcessor {
let lastActivityTime = Date.now();
while (!signal.aborted) {
// Claim phase: atomically claim next pending message (marks as 'processing')
// Self-heals any stale processing messages before claiming
let persistentMessage: PersistentPendingMessage | null = null;
try {
// Atomically claim next pending message (marks as 'processing')
// Self-heals any stale processing messages before claiming
const persistentMessage = this.store.claimNextMessage(sessionDbId);
if (persistentMessage) {
// Reset activity time when we successfully yield a message
lastActivityTime = Date.now();
// Yield the message for processing (it's marked as 'processing' in DB)
yield this.toPendingMessageWithId(persistentMessage);
} else {
// Queue empty - wait for wake-up event or timeout
const receivedMessage = await this.waitForMessage(signal, IDLE_TIMEOUT_MS);
if (!receivedMessage && !signal.aborted) {
// Timeout occurred - check if we've been idle too long
const idleDuration = Date.now() - lastActivityTime;
if (idleDuration >= IDLE_TIMEOUT_MS) {
logger.info('SESSION', 'Idle timeout reached, triggering abort to kill subprocess', {
sessionDbId,
idleDurationMs: idleDuration,
thresholdMs: IDLE_TIMEOUT_MS
});
onIdleTimeout?.();
return;
}
// Reset timer on spurious wakeup - queue is empty but duration check failed
lastActivityTime = Date.now();
}
}
persistentMessage = this.store.claimNextMessage(sessionDbId);
} catch (error) {
if (signal.aborted) return;
logger.error('SESSION', 'Error in queue processor loop', { sessionDbId }, error as Error);
// Small backoff to prevent tight loop on DB error
const normalizedError = error instanceof Error ? error : new Error(String(error));
logger.error('QUEUE', 'Failed to claim next message', { sessionDbId }, normalizedError);
await new Promise(resolve => setTimeout(resolve, 1000));
continue;
}
if (persistentMessage) {
// Reset activity time when we successfully yield a message
lastActivityTime = Date.now();
// Yield the message for processing (it's marked as 'processing' in DB)
yield this.toPendingMessageWithId(persistentMessage);
continue;
}
// Wait phase: queue empty - wait for wake-up event or timeout
try {
const idleTimedOut = await this.handleWaitPhase(signal, lastActivityTime, sessionDbId, onIdleTimeout);
if (idleTimedOut) return;
// Reset timer on spurious wakeup if not timed out
lastActivityTime = Date.now();
} catch (error) {
if (signal.aborted) return;
const normalizedError = error instanceof Error ? error : new Error(String(error));
logger.error('QUEUE', 'Error waiting for message', { sessionDbId }, normalizedError);
// Small backoff to prevent tight loop on error
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
@@ -82,6 +80,33 @@ export class SessionQueueProcessor {
};
}
/**
* Handle the wait phase: wait for a message or check idle timeout.
* @returns true if idle timeout was reached (caller should return/exit iterator)
*/
private async handleWaitPhase(
signal: AbortSignal,
lastActivityTime: number,
sessionDbId: number,
onIdleTimeout?: () => void
): Promise<boolean> {
const receivedMessage = await this.waitForMessage(signal, IDLE_TIMEOUT_MS);
if (!receivedMessage && !signal.aborted) {
const idleDuration = Date.now() - lastActivityTime;
if (idleDuration >= IDLE_TIMEOUT_MS) {
logger.info('SESSION', 'Idle timeout reached, triggering abort to kill subprocess', {
sessionDbId,
idleDurationMs: idleDuration,
thresholdMs: IDLE_TIMEOUT_MS
});
onIdleTimeout?.();
return true;
}
}
return false;
}
/**
* Wait for a message event or timeout.
* @param signal - AbortSignal to cancel waiting