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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user