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:
+50
-50
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user