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
+50 -50
View File
@@ -121,8 +121,8 @@ export function loadClaudeMemEnv(): ClaudeMemEnv {
if (parsed.OPENROUTER_API_KEY) result.OPENROUTER_API_KEY = parsed.OPENROUTER_API_KEY;
return result;
} catch (error) {
logger.warn('ENV', 'Failed to load .env file', { path: ENV_FILE_PATH }, error as Error);
} catch (error: unknown) {
logger.warn('ENV', 'Failed to load .env file', { path: ENV_FILE_PATH }, error instanceof Error ? error : new Error(String(error)));
return {};
}
}
@@ -131,60 +131,60 @@ export function loadClaudeMemEnv(): ClaudeMemEnv {
* Save credentials to ~/.claude-mem/.env
*/
export function saveClaudeMemEnv(env: ClaudeMemEnv): void {
// Ensure directory exists with restricted permissions (owner only)
if (!existsSync(DATA_DIR)) {
mkdirSync(DATA_DIR, { recursive: true, mode: 0o700 });
}
// Fix permissions on pre-existing directories (mode: is only applied on creation)
// Note: On Windows, chmod has no effect — permissions are controlled via ACLs.
chmodSync(DATA_DIR, 0o700);
// Load existing to preserve any extra keys
const existing = existsSync(ENV_FILE_PATH)
? parseEnvFile(readFileSync(ENV_FILE_PATH, 'utf-8'))
: {};
// Update with new values
const updated: Record<string, string> = { ...existing };
// Only update managed keys
if (env.ANTHROPIC_API_KEY !== undefined) {
if (env.ANTHROPIC_API_KEY) {
updated.ANTHROPIC_API_KEY = env.ANTHROPIC_API_KEY;
} else {
delete updated.ANTHROPIC_API_KEY;
}
}
if (env.ANTHROPIC_BASE_URL !== undefined) {
if (env.ANTHROPIC_BASE_URL) {
updated.ANTHROPIC_BASE_URL = env.ANTHROPIC_BASE_URL;
} else {
delete updated.ANTHROPIC_BASE_URL;
}
}
if (env.GEMINI_API_KEY !== undefined) {
if (env.GEMINI_API_KEY) {
updated.GEMINI_API_KEY = env.GEMINI_API_KEY;
} else {
delete updated.GEMINI_API_KEY;
}
}
if (env.OPENROUTER_API_KEY !== undefined) {
if (env.OPENROUTER_API_KEY) {
updated.OPENROUTER_API_KEY = env.OPENROUTER_API_KEY;
} else {
delete updated.OPENROUTER_API_KEY;
}
}
try {
// Ensure directory exists with restricted permissions (owner only)
if (!existsSync(DATA_DIR)) {
mkdirSync(DATA_DIR, { recursive: true, mode: 0o700 });
}
// Fix permissions on pre-existing directories (mode: is only applied on creation)
// Note: On Windows, chmod has no effect — permissions are controlled via ACLs.
chmodSync(DATA_DIR, 0o700);
// Load existing to preserve any extra keys
const existing = existsSync(ENV_FILE_PATH)
? parseEnvFile(readFileSync(ENV_FILE_PATH, 'utf-8'))
: {};
// Update with new values
const updated: Record<string, string> = { ...existing };
// Only update managed keys
if (env.ANTHROPIC_API_KEY !== undefined) {
if (env.ANTHROPIC_API_KEY) {
updated.ANTHROPIC_API_KEY = env.ANTHROPIC_API_KEY;
} else {
delete updated.ANTHROPIC_API_KEY;
}
}
if (env.ANTHROPIC_BASE_URL !== undefined) {
if (env.ANTHROPIC_BASE_URL) {
updated.ANTHROPIC_BASE_URL = env.ANTHROPIC_BASE_URL;
} else {
delete updated.ANTHROPIC_BASE_URL;
}
}
if (env.GEMINI_API_KEY !== undefined) {
if (env.GEMINI_API_KEY) {
updated.GEMINI_API_KEY = env.GEMINI_API_KEY;
} else {
delete updated.GEMINI_API_KEY;
}
}
if (env.OPENROUTER_API_KEY !== undefined) {
if (env.OPENROUTER_API_KEY) {
updated.OPENROUTER_API_KEY = env.OPENROUTER_API_KEY;
} else {
delete updated.OPENROUTER_API_KEY;
}
}
writeFileSync(ENV_FILE_PATH, serializeEnvFile(updated), { encoding: 'utf-8', mode: 0o600 });
// Explicitly set permissions in case the file already existed before this fix.
// writeFileSync's mode option only applies on file creation (O_CREAT), not on overwrites.
// Note: On Windows, chmod has no effect — permissions are controlled via ACLs.
chmodSync(ENV_FILE_PATH, 0o600);
} catch (error) {
logger.error('ENV', 'Failed to save .env file', { path: ENV_FILE_PATH }, error as Error);
} catch (error: unknown) {
logger.error('ENV', 'Failed to save .env file', { path: ENV_FILE_PATH }, error instanceof Error ? error : new Error(String(error)));
throw error;
}
}
+6 -6
View File
@@ -221,8 +221,8 @@ export class SettingsDefaultsManager {
writeFileSync(settingsPath, JSON.stringify(defaults, null, 2), 'utf-8');
// Use console instead of logger to avoid circular dependency
console.log('[SETTINGS] Created settings file with defaults:', settingsPath);
} catch (error) {
console.warn('[SETTINGS] Failed to create settings file, using in-memory defaults:', settingsPath, error);
} catch (error: unknown) {
console.warn('[SETTINGS] Failed to create settings file, using in-memory defaults:', settingsPath, error instanceof Error ? error.message : String(error));
}
// Still apply env var overrides even when file doesn't exist
return this.applyEnvOverrides(defaults);
@@ -241,8 +241,8 @@ export class SettingsDefaultsManager {
try {
writeFileSync(settingsPath, JSON.stringify(flatSettings, null, 2), 'utf-8');
console.log('[SETTINGS] Migrated settings file from nested to flat schema:', settingsPath);
} catch (error) {
console.warn('[SETTINGS] Failed to auto-migrate settings file:', settingsPath, error);
} catch (error: unknown) {
console.warn('[SETTINGS] Failed to auto-migrate settings file:', settingsPath, error instanceof Error ? error.message : String(error));
// Continue with in-memory migration even if write fails
}
}
@@ -257,8 +257,8 @@ export class SettingsDefaultsManager {
// Apply environment variable overrides (highest priority)
return this.applyEnvOverrides(result);
} catch (error) {
console.warn('[SETTINGS] Failed to load settings, using defaults:', settingsPath, error);
} catch (error: unknown) {
console.warn('[SETTINGS] Failed to load settings, using defaults:', settingsPath, error instanceof Error ? error.message : String(error));
// Still apply env var overrides even on error
return this.applyEnvOverrides(this.getAllDefaults());
}
+2 -2
View File
@@ -146,10 +146,10 @@ export function getCurrentProjectName(): string {
windowsHide: true
}).trim();
return basename(dirname(gitRoot)) + '/' + basename(gitRoot);
} catch (error) {
} catch (error: unknown) {
logger.debug('SYSTEM', 'Git root detection failed, using cwd basename', {
cwd: process.cwd()
}, error as Error);
}, error instanceof Error ? error : new Error(String(error)));
const cwd = process.cwd();
return basename(dirname(cwd)) + '/' + basename(cwd);
}
+2 -1
View File
@@ -22,8 +22,9 @@ export function isPluginDisabledInClaudeSettings(): boolean {
const raw = readFileSync(settingsPath, 'utf-8');
const settings = JSON.parse(raw);
return settings?.enabledPlugins?.[PLUGIN_SETTINGS_KEY] === false;
} catch {
} catch (error: unknown) {
// If settings can't be read/parsed, assume not disabled
console.error('[plugin-state] Failed to read Claude settings:', error instanceof Error ? error.message : String(error));
return false;
}
}
+2 -2
View File
@@ -16,10 +16,10 @@ export function parseJsonArray(json: string | null): string[] {
try {
const parsed = JSON.parse(json);
return Array.isArray(parsed) ? parsed : [];
} catch (err) {
} catch (err: unknown) {
logger.debug('PARSER', 'Failed to parse JSON array, using empty fallback', {
preview: json?.substring(0, 50)
}, err as Error);
}, err instanceof Error ? err : new Error(String(err)));
return [];
}
}
+31 -22
View File
@@ -148,7 +148,7 @@ function getPluginVersion(): string {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
return packageJson.version;
} catch (error: unknown) {
const code = (error as NodeJS.ErrnoException).code;
const code = error instanceof Error ? (error as NodeJS.ErrnoException).code : undefined;
if (code === 'ENOENT' || code === 'EBUSY') {
logger.debug('SYSTEM', 'Could not read plugin version (shutdown race)', { code });
return 'unknown';
@@ -176,30 +176,39 @@ async function getWorkerVersion(): Promise<string> {
* Skips comparison when either version is 'unknown' (fix #1042 — avoids restart loops).
*/
async function checkWorkerVersion(): Promise<void> {
let pluginVersion: string;
try {
const pluginVersion = getPluginVersion();
// Skip version check if plugin version couldn't be read (shutdown race)
if (pluginVersion === 'unknown') return;
const workerVersion = await getWorkerVersion();
// Skip version check if worker version is 'unknown' (avoids restart loops)
if (workerVersion === 'unknown') return;
if (pluginVersion !== workerVersion) {
// Just log debug info - auto-restart handles the mismatch in worker-service.ts
logger.debug('SYSTEM', 'Version check', {
pluginVersion,
workerVersion,
note: 'Mismatch will be auto-restarted by worker-service start command'
});
}
} catch (error) {
// Version check is informational — don't fail the hook
logger.debug('SYSTEM', 'Version check failed', {
pluginVersion = getPluginVersion();
} catch (error: unknown) {
logger.debug('SYSTEM', 'Version check failed reading plugin version', {
error: error instanceof Error ? error.message : String(error)
});
return;
}
// Skip version check if plugin version couldn't be read (shutdown race)
if (pluginVersion === 'unknown') return;
let workerVersion: string;
try {
workerVersion = await getWorkerVersion();
} catch (error: unknown) {
logger.debug('SYSTEM', 'Version check failed reading worker version', {
error: error instanceof Error ? error.message : String(error)
});
return;
}
// Skip version check if worker version is 'unknown' (avoids restart loops)
if (workerVersion === 'unknown') return;
if (pluginVersion !== workerVersion) {
// Just log debug info - auto-restart handles the mismatch in worker-service.ts
logger.debug('SYSTEM', 'Version check', {
pluginVersion,
workerVersion,
note: 'Mismatch will be auto-restarted by worker-service start command'
});
}
}