From dfff304a961efc8b7c4bb7d7c2412ee92a227e3d Mon Sep 17 00:00:00 2001 From: Tony Casey Date: Sun, 15 Feb 2026 21:47:16 +0000 Subject: [PATCH] fix: handle circular error references in logger and handlers (GIT-109) Logger JSON.stringify crashed on Error objects with circular references, causing the CommitMsgHandler to fail silently and skip trailers. Added safeStringify with circular reference detection and Error serialization to the Logger. Also updated all five hook handlers to extract error message/stack before passing to logger context instead of raw Error objects. Co-Authored-By: Claude Opus 4.6 AI-Agent: Claude-Code/2.1.42 --- src/application/handlers/CommitMsgHandler.ts | 8 ++++++-- src/application/handlers/PostCommitHandler.ts | 8 ++++++-- src/application/handlers/PromptSubmitHandler.ts | 8 ++++++-- src/application/handlers/SessionStartHandler.ts | 8 ++++++-- src/application/handlers/SessionStopHandler.ts | 8 ++++++-- src/infrastructure/logging/Logger.ts | 17 ++++++++++++++++- 6 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/application/handlers/CommitMsgHandler.ts b/src/application/handlers/CommitMsgHandler.ts index f30e5b1c..dfee6d6b 100644 --- a/src/application/handlers/CommitMsgHandler.ts +++ b/src/application/handlers/CommitMsgHandler.ts @@ -122,11 +122,15 @@ export class CommitMsgHandler implements IEventHandler { success: true, }; } catch (error) { - this.logger.error('Failed to process commit-msg hook', { error }); + const err = error instanceof Error ? error : new Error(String(error)); + this.logger.error('Failed to process commit-msg hook', { + error: err.message, + stack: err.stack, + }); return { handler: 'CommitMsgHandler', success: false, - error: error instanceof Error ? error : new Error(String(error)), + error: err, }; } } diff --git a/src/application/handlers/PostCommitHandler.ts b/src/application/handlers/PostCommitHandler.ts index 29ba17c0..dd7810a9 100644 --- a/src/application/handlers/PostCommitHandler.ts +++ b/src/application/handlers/PostCommitHandler.ts @@ -81,11 +81,15 @@ export class PostCommitHandler implements IPostCommitHandler { success: true, }; } catch (error) { - this.logger?.error('Post-commit handler failed', { error }); + const err = error instanceof Error ? error : new Error(String(error)); + this.logger?.error('Post-commit handler failed', { + error: err.message, + stack: err.stack, + }); return { handler: 'PostCommitHandler', success: false, - error: error instanceof Error ? error : new Error(String(error)), + error: err, }; } } diff --git a/src/application/handlers/PromptSubmitHandler.ts b/src/application/handlers/PromptSubmitHandler.ts index c645da67..30e3a640 100644 --- a/src/application/handlers/PromptSubmitHandler.ts +++ b/src/application/handlers/PromptSubmitHandler.ts @@ -121,11 +121,15 @@ export class PromptSubmitHandler implements IPromptSubmitHandler { output, }; } catch (error) { - this.logger?.error('Prompt submit handler failed', { error }); + const err = error instanceof Error ? error : new Error(String(error)); + this.logger?.error('Prompt submit handler failed', { + error: err.message, + stack: err.stack, + }); return { handler: 'PromptSubmitHandler', success: false, - error: error instanceof Error ? error : new Error(String(error)), + error: err, }; } } diff --git a/src/application/handlers/SessionStartHandler.ts b/src/application/handlers/SessionStartHandler.ts index 5be99a9b..fa5f4b46 100644 --- a/src/application/handlers/SessionStartHandler.ts +++ b/src/application/handlers/SessionStartHandler.ts @@ -53,11 +53,15 @@ export class SessionStartHandler implements ISessionStartHandler { output, }; } catch (error) { - this.logger?.error('Session start handler failed', { error }); + const err = error instanceof Error ? error : new Error(String(error)); + this.logger?.error('Session start handler failed', { + error: err.message, + stack: err.stack, + }); return { handler: 'SessionStartHandler', success: false, - error: error instanceof Error ? error : new Error(String(error)), + error: err, }; } } diff --git a/src/application/handlers/SessionStopHandler.ts b/src/application/handlers/SessionStopHandler.ts index b93b9126..49b65b2b 100644 --- a/src/application/handlers/SessionStopHandler.ts +++ b/src/application/handlers/SessionStopHandler.ts @@ -40,11 +40,15 @@ export class SessionStopHandler implements ISessionStopHandler { output: result.summary, }; } catch (error) { - this.logger?.error('Session stop handler failed', { error }); + const err = error instanceof Error ? error : new Error(String(error)); + this.logger?.error('Session stop handler failed', { + error: err.message, + stack: err.stack, + }); return { handler: 'SessionStopHandler', success: false, - error: error instanceof Error ? error : new Error(String(error)), + error: err, }; } } diff --git a/src/infrastructure/logging/Logger.ts b/src/infrastructure/logging/Logger.ts index 12e2f5f3..5938b574 100644 --- a/src/infrastructure/logging/Logger.ts +++ b/src/infrastructure/logging/Logger.ts @@ -22,6 +22,21 @@ const LEVEL_COLORS: Record = { const RESET = '\x1b[0m'; +/** JSON.stringify that handles circular references and Error objects. */ +function safeStringify(obj: unknown): string { + const seen = new WeakSet(); + return JSON.stringify(obj, (_key, value) => { + if (value instanceof Error) { + return { message: value.message, stack: value.stack }; + } + if (typeof value === 'object' && value !== null) { + if (seen.has(value)) return '[Circular]'; + seen.add(value); + } + return value; + }); +} + function formatTimestamp(): string { const now = new Date(); const pad = (n: number, len = 2) => String(n).padStart(len, '0'); @@ -85,7 +100,7 @@ export class Logger implements ILogger { if (!this.isLevelEnabled(level)) return; const merged = { ...this.bindings, ...context }; - const contextStr = Object.keys(merged).length > 0 ? ` ${JSON.stringify(merged)}` : ''; + const contextStr = Object.keys(merged).length > 0 ? ` ${safeStringify(merged)}` : ''; const timestamp = formatTimestamp(); const levelUpper = level.toUpperCase().padEnd(5);