Skip to content

fix: replace empty catch blocks with structured error handling#764

Merged
carlos-alm merged 10 commits intomainfrom
fix/empty-catch-blocks
Apr 3, 2026
Merged

fix: replace empty catch blocks with structured error handling#764
carlos-alm merged 10 commits intomainfrom
fix/empty-catch-blocks

Conversation

@carlos-alm
Copy link
Copy Markdown
Contributor

Summary

  • Replace empty catch blocks with structured error handling across 10 files
  • Add suppressExpected error-handling helper in shared/errors.ts for intentional catch suppression
  • Establishes consistent pattern for error handling across the codebase

Titan Audit Context

  • Phase: quality_fix + abstraction
  • Domain: cross-cutting (19 files had 85+ empty catches)
  • Commits: 2
  • Depends on: none (merge first)

Changes

  • src/ast-analysis/engine.ts - Replace empty catches with logger.debug
  • src/db/connection.ts - Replace empty catches with logger.debug
  • src/domain/graph/builder/pipeline.ts - Replace empty catches with logger.debug
  • src/domain/graph/builder/stages/insert-nodes.ts - Replace empty catches with logger.debug
  • src/features/audit.ts - Replace empty catches with logger.debug
  • src/features/branch-compare.ts - Replace empty catches with logger.debug
  • src/infrastructure/config.ts - Replace empty catches with logger.debug
  • src/mcp/server.ts - Replace empty catches with logger.debug
  • src/presentation/queries-cli/overview.ts - Replace empty catches with logger.debug
  • src/presentation/viewer.ts - Replace empty catches with logger.debug
  • src/shared/errors.ts - Add suppressExpected helper

Metrics Impact

  • Pillar II (Rule 10: emptyCatch) violations addressed: 19 files -> 0
  • No complexity regressions

Test plan

  • CI passes (lint + build + tests)
  • No new functions above complexity thresholds

Replace catch blocks that silently swallow errors with proper error
variable capture and debug() logging across 10 files. This addresses
Pillar II (Rule 10: emptyCatch) violations identified by the gauntlet
audit. Files that already had proper error handling were verified and
left unchanged.
@claude
Copy link
Copy Markdown

claude bot commented Apr 3, 2026

Claude finished @carlos-alm's task —— View job


Reviewing PR: Empty Catch Blocks Fix

I'll conduct a comprehensive review of this PR following the 5-phase analysis framework.

Todo List:

  • Phase 1: Root cause analysis - understand the problem being solved
  • Phase 2: Configuration change detection - check for rule relaxation
  • Phase 3: Test integrity - verify no weakened tests
  • Phase 4: Code quality review - analyze implementation
  • Phase 5: Backlog compliance grading
  • Provide final assessment and rating

Phase 1: Root Cause Analysis ✅

Problem Being Solved: The PR addresses empty catch blocks across the codebase that were likely flagged by linting rules (Biome Rule 10: emptyCatch). Empty catch blocks silently swallow errors without logging or handling them appropriately.

Why This Approach: The PR takes a structured approach by:

  1. Adding helper functions (suppressError, suppressErrorAsync) in shared/errors.ts for intentional error suppression
  2. Replacing empty catches with debug() logging that provides context about what failed
  3. Following a consistent pattern across all files

Risk Assessment: ✅ LOW RISK - This is genuine error handling improvement, not rule suppression.

Phase 2: Configuration Change Detection ✅

No configuration changes detected:

  • ❌ No biome.json, .eslintrc, or tsconfig.json modifications
  • ❌ No package.json dependency changes
  • ❌ No relaxed linting rules or disabled warnings

Phase 3: Test Integrity ✅

No test files modified:

  • ❌ No .test.ts or .spec.ts files in the changeset
  • ❌ No weakened assertions or conditional test logic
  • All changes are in production code only

Phase 4: Code Quality Review ✅

Excellent implementation quality:

  1. Helper functions are well-designed (src/shared/errors.ts:83-119):

    • Clear JSDoc with examples
    • Proper TypeScript generics for type safety
    • Both sync and async variants
    • Centralized error message extraction with toErrorMessage()
  2. Consistent application pattern:

    • All empty catch {} blocks replaced with catch (e) { debug(...) }
    • Contextual error messages (e.g., "pre-collect WAL checkpoint failed")
    • Proper error casting: (e as Error).message
  3. No security concerns:

    • Debug logging is appropriate for these scenarios (DB cleanup, optional operations)
    • No sensitive data exposed in error messages
    • No command injection or SQL injection vectors
  4. Performance neutral:

    • Debug logging is conditional (only runs when debug enabled)
    • No additional async operations or heavy computation

Specific code quality observations:

  • src/domain/graph/builder/pipeline.ts:50-60: WAL checkpoint failures are now logged with context
  • src/infrastructure/config.ts:255: Directory read failures properly logged
  • src/features/audit.ts:45: Config loading failures logged with fallback explanation

Phase 5: Backlog Compliance

Root Cause Analysis

Problem Being Solved: Eliminates silent error swallowing via empty catch blocks, replacing with structured logging for debugging and monitoring.
Why This Approach: Provides observability into previously hidden failures while maintaining graceful degradation for non-critical operations.
Risk Assessment: Low risk - improves error handling without changing behavior.

Backlog Compliance

  • Zero-dep: ✓ — No new runtime dependencies, uses existing logger infrastructure
  • Foundation-aligned: ✓ — Improves observability without violating core principles
  • Problem-fit: 3/5 — Code quality improvement that aids debugging but doesn't directly address AI navigation issues
  • Breaking: No — Purely additive logging, no API or behavior changes
  • Tier: 1

Critical Concerns

None identified. The implementation follows best practices and improves code quality without introducing risks.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: High-quality implementation that genuinely improves error handling. The suppressError helpers are well-designed and could be useful throughout the codebase. The consistent application pattern shows careful attention to detail. Only missing the 5th star because this is primarily a code quality improvement rather than a feature that directly addresses the core problem statement.

Additional notes:

  • The helper functions aren't used in this PR but provide a good foundation for future intentional error suppression
  • All error messages provide helpful context for debugging
  • No behavioral changes that could affect existing functionality

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 3, 2026

Greptile Summary

This PR completes the structured error-handling cleanup by replacing every remaining (e as Error).message unsafe cast with toErrorMessage(e) from shared/errors.ts across 10 files, and relocates the suppressError/suppressErrorAsync catch-suppression helpers to infrastructure/suppress.ts (keeping shared/errors.ts dependency-free). The previous P1 concerns raised in review — unsafe non-Error casts and the unused helper API — are both fully addressed.

  • All catch blocks now use toErrorMessage(e), which safely handles non-Error thrown values (strings, null, plain objects) via String(e) fallback.
  • suppressError/suppressErrorAsync helpers are properly placed in infrastructure/suppress.ts with the logger dependency, with a forwarding JSDoc in shared/errors.ts directing callers to the right location.
  • The one deliberate variation in mcp/server.ts (using err.stack ?? err.message on the fatal-exit path) is intentional and superior — it preserves the full stack trace for the one location where the process terminates immediately.
  • The ErrnoException.code || toErrorMessage(e) idiom in db/connection.ts is correctly preserved for the PID-liveness check where the errno code is more informative than the message.

Confidence Score: 5/5

All prior P1 concerns resolved; no new issues found — safe to merge.

The two P1 findings from the previous review round (unsafe non-Error casts and the unused helper API) are both fully addressed. The changes are purely mechanical substitutions of a well-tested utility, with no logic regressions. The one deliberate divergence in mcp/server.ts (stack trace on fatal exit) is an improvement, not a gap.

No files require special attention.

Important Files Changed

Filename Overview
src/shared/errors.ts JSDoc comment added directing users to infrastructure/suppress.ts for catch-suppression helpers; toErrorMessage unchanged.
src/db/connection.ts All 13 catch blocks updated to use toErrorMessage; the special `ErrnoException.code
src/mcp/server.ts All catch blocks updated to toErrorMessage; the fatal-exit path at line 251 uses err.stack ?? err.message for Error instances to preserve full stack — intentional and more informative than the message alone.
src/ast-analysis/engine.ts Eight catch blocks across native analysis helpers updated to toErrorMessage; no logic changes.
src/domain/graph/builder/pipeline.ts Seven catch blocks updated to toErrorMessage; WAL checkpoint and native fallback paths correctly preserved.
src/domain/graph/builder/stages/insert-nodes.ts Five catch blocks updated to toErrorMessage; hash-building and native-insert fallback paths unchanged.
src/features/audit.ts Three catch blocks updated to toErrorMessage; threshold resolution and health-check fallback logic unchanged.
src/features/branch-compare.ts Seven catch blocks updated; the top-level catch (err) { return { error: toErrorMessage(err) } } is now safe for non-Error rejections.
src/infrastructure/config.ts Four catch blocks updated to toErrorMessage; config parsing and secret resolution logic unchanged.
src/presentation/queries-cli/overview.ts Single catch block updated to toErrorMessage; community detection optional path unchanged.
src/presentation/viewer.ts Single catch block updated to toErrorMessage; JSON config parse fallback unchanged.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[catch receives unknown] --> B{e instanceof Error?}
    B -- Yes --> C[e.message]
    B -- No --> D["String(e)"]
    C --> E[toErrorMessage result]
    D --> E

    E --> F{Call site}
    F -- debug log --> G[debug context + message]
    F -- warn log --> H[warn context + message]
    F -- fatal exit --> I["err.stack ?? err.message"]
    F -- errno code --> J["ErrnoException.code or toErrorMessage"]
    F -- return value --> K["return error string"]

    subgraph suppress["suppress.ts helpers"]
        L["suppressError / suppressErrorAsync"] --> M[try fn]
        M -- throws --> N[debug + return fallback]
        M -- ok --> O[return result]
    end
Loading

Reviews (3): Last reviewed commit: "fix: replace remaining unsafe (err as Er..." | Re-trigger Greptile

Comment on lines +92 to +119
export function suppressError<T>(fn: () => T, context: string, fallback: T): T {
try {
return fn();
} catch (e: unknown) {
debug(`${context}: ${toErrorMessage(e)}`);
return fallback;
}
}

/**
* Async variant of {@link suppressError}. Awaits `fn()` and returns `fallback`
* on rejection, logging the error via `debug()`.
*
* @example
* const data = await suppressErrorAsync(() => fetchOptionalData(), 'fetch data', null);
*/
export async function suppressErrorAsync<T>(
fn: () => Promise<T>,
context: string,
fallback: T,
): Promise<T> {
try {
return await fn();
} catch (e: unknown) {
debug(`${context}: ${toErrorMessage(e)}`);
return fallback;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 suppressError helpers added but never used

The PR adds suppressError and suppressErrorAsync as reusable helpers, and the JSDoc + design are solid. However, none of the 10 changed files actually call them — every catch block is replaced with the inline catch (e) { debug(...) } pattern instead. This means two competing idioms now coexist:

  1. suppressError(() => fn(), 'ctx', fallback) — uses toErrorMessage(e) (safe for non-Error throws)
  2. catch (e) { debug(\ctx: ${(e as Error).message}`) }— unsafe cast, returnsundefined` message if a non-Error is thrown

Either apply the helper uniformly (replacing the inline patterns), or don't ship the helper until it's actually adopted. Shipping an abstraction that isn't used creates maintenance confusion about which pattern is canonical.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed the unsafe cast issue (all 10 files now use toErrorMessage(e) consistently). Regarding the unused helpers: suppressError/suppressErrorAsync are intentionally shipped as available utilities for future adoption — they provide a higher-level API over the inline pattern. The codebase now has one canonical safety pattern (toErrorMessage) used everywhere, and the helpers build on it. Removing them would mean re-adding them later when callers want the wrap-and-fallback pattern.

Comment on lines 43 to 44
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unsafe (e as Error).message cast throughout PR

Every changed catch block uses (e as Error).message. If the thrown value is not an Error instance (e.g. a rejected promise with a plain string, null, or a plain object), .message evaluates to undefined, producing an uninformative debug line like "Version mismatch check skipped (build_meta may not exist): undefined".

The codebase already has toErrorMessage(e) in shared/errors.ts and the new suppressError helper uses it correctly. The inline catch blocks should do the same:

Suggested change
);
}
debug(`Version mismatch check skipped (build_meta may not exist): ${toErrorMessage(e)}`);

The same applies in every other catch block added in this PR (src/ast-analysis/engine.ts, src/domain/graph/builder/pipeline.ts, src/domain/graph/builder/stages/insert-nodes.ts, src/features/audit.ts, src/features/branch-compare.ts, src/infrastructure/config.ts, src/mcp/server.ts, src/presentation/queries-cli/overview.ts, src/presentation/viewer.ts).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. All 10 files now use toErrorMessage(e) from shared/errors.ts instead of the unsafe (e as Error).message cast. Each file that didn't already import it now does. The special case in connection.ts (line 107) preserves the ErrnoException.code check while using toErrorMessage(e) as the fallback: (e as NodeJS.ErrnoException).code || toErrorMessage(e).

@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

* fix: address remaining quality issues across domains

Extract sub-functions to reduce halsteadEffort and cognitive complexity
in 11 FAIL targets: dependencies, communities, server, parser, context,
overview, impact, exports, path, viewer, dataflow. All functions now
pass gate thresholds (cognitive <= 30, halstead.bugs <= 1.0, MI >= 20).

* fix: correct hc parameter type in buildNodeDepsResult (#774)

Change Map<unknown, unknown> to Map<string, string | null> to match
normalizeSymbol's hashCache parameter type, fixing a TS2345 build error.
)

* refactor: extract sub-functions from features domain god-functions

* refactor: split buildAstNodes and fix console.log usage

* fix: resolve TS2345 type error in buildNativeDataflowResult parameter (#772)

Use NativeDatabase type directly instead of inline type literal, with
non-null assertion on getDataflowEdges since the call site guards with
an existence check.

* fix: remove unused _baseSymbols parameter from attachImpactToSymbols (#772)

The parameter was accepted but never read. Drop it from the signature
and call site as flagged by Greptile review.

* fix: add clarifying comment for handleBranchNode partial-handling return (#772)

Document that returning false after incrementing accumulators is
intentional — the caller continues with remaining checks (pattern-C
else, case nodes, walkChildren).
@carlos-alm
Copy link
Copy Markdown
Contributor Author

Addressed the 4 remaining unsafe casts identified in the re-review:

  • src/features/branch-compare.ts lines 60, 69, 516, 523: replaced (rmErr as Error).message, (pruneErr as Error).message, (err as Error).message, and (cleanupErr as Error).message with toErrorMessage()
  • src/mcp/server.ts line 130: replaced (shutdownErr as Error).message with toErrorMessage(shutdownErr)
  • Also replaced remaining unsafe casts in mcp/server.ts lines 196 and 254, and in the pipeline helper functions (closeNativeDb, reopenNativeDb, refreshJsDb) that were introduced by the Titan v3.8.0 refactor (refactor: Titan v3.8.0 — decompose god-functions, structured logging, error handling #775)
  • Also fixed remaining (err as Error).message casts in engine.ts and config.ts that were introduced by the same refactor

Merged origin/main to resolve conflicts. All 2275 core tests pass, TypeScript compiles cleanly.

@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

Extract repeated patterns into focused helper functions:
- pipeline.ts: closeNativeDb, reopenNativeDb, suspendNativeDb, refreshJsDb
- insert-nodes.ts: marshalSymbolBatches, buildFileHashes
- resolve-imports.ts: buildReexportMap, findBarrelCandidates, reparseBarrelFiles
- watcher.ts: prepareWatcherStatements, processPendingFiles, writeJournalAndChangeEvents
@carlos-alm carlos-alm merged commit 07b4714 into main Apr 3, 2026
12 of 13 checks passed
@carlos-alm carlos-alm deleted the fix/empty-catch-blocks branch April 3, 2026 04:46
@github-actions github-actions bot locked and limited conversation to collaborators Apr 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant