From 6a7b7f667061f1e8488b03a0e7c753ccfa6f2dd7 Mon Sep 17 00:00:00 2001 From: Gokhan Ozdemir Date: Sat, 5 Sep 2026 19:52:18 +0300 Subject: [PATCH] fix(agents): add file_not_found correlation status for missing transcripts Interactive Claude sessions don't persist transcripts at the reported path for PTY-driven sessions. The correlation logic now checks if the transcript file actually exists on disk before marking it as 'matched'. If the file doesn't exist, correlation status is set to 'file_not_found' instead, preventing metrics processing failures and providing better error handling for interactive sessions where transcript persistence is expected to be absent. Fixes #523 --- src/agents/core/session/types.ts | 2 +- src/cli/commands/hook.ts | 32 +++++++++++++++++-- .../plugins/sso/session/SessionSyncer.ts | 13 ++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/agents/core/session/types.ts b/src/agents/core/session/types.ts index 2d5b62418..a4dbb0497 100644 --- a/src/agents/core/session/types.ts +++ b/src/agents/core/session/types.ts @@ -17,7 +17,7 @@ export interface BaseNormalizedMessage { /** * Correlation status */ -export type CorrelationStatus = 'pending' | 'matched' | 'failed'; +export type CorrelationStatus = 'pending' | 'matched' | 'failed' | 'file_not_found'; /** * Correlation result diff --git a/src/cli/commands/hook.ts b/src/cli/commands/hook.ts index 5e91b9fed..e0825c8ba 100644 --- a/src/cli/commands/hook.ts +++ b/src/cli/commands/hook.ts @@ -807,9 +807,22 @@ async function createSessionRecord(event: SessionStartEvent, sessionId: string, existing.status = 'active'; if (gitBranch) existing.gitBranch = gitBranch; if (remoteRepository) existing.repository = remoteRepository; + + // Check if the transcript file actually exists on disk before marking correlation as matched + const { existsSync } = await import('node:fs'); + const transcriptFileExists = event.transcript_path ? existsSync(event.transcript_path) : false; + const correlationStatus = transcriptFileExists ? 'matched' as const : 'file_not_found' as const; + + if (!transcriptFileExists && event.transcript_path) { + logger.warn( + `[hook:SessionStart] Transcript path reported but file does not exist: ${event.transcript_path}. ` + + `Correlation status set to 'file_not_found' to prevent metrics processing failures.` + ); + } + existing.correlation = { ...existing.correlation, - status: 'matched', + status: correlationStatus, ...(event.session_id && { agentSessionId: event.session_id }), ...(event.transcript_path && { agentSessionFile: event.transcript_path }), }; @@ -837,12 +850,25 @@ async function createSessionRecord(event: SessionStartEvent, sessionId: string, const { appendTranscriptMarker, appendAuditEvent, isExternalOrigin } = await import( '../../agents/core/session/session-origin-audit.js' ); + const { existsSync } = await import('node:fs'); const origin = getConfigValue(SESSION_ORIGIN_ENV_KEY, config) === SESSION_ORIGIN.EXTERNAL_RESUME ? SESSION_ORIGIN.EXTERNAL_RESUME : undefined; - // Create session record with correlation already matched + // Check if the transcript file actually exists on disk before marking correlation as matched + // This prevents metrics processing from failing on non-existent files (e.g., interactive PTY sessions) + const transcriptFileExists = event.transcript_path ? existsSync(event.transcript_path) : false; + const correlationStatus = transcriptFileExists ? 'matched' as const : 'file_not_found' as const; + + if (!transcriptFileExists && event.transcript_path) { + logger.warn( + `[hook:SessionStart] Transcript path reported but file does not exist: ${event.transcript_path}. ` + + `Correlation status set to 'file_not_found' to prevent metrics processing failures.` + ); + } + + // Create session record with correlation status based on file existence const session = { sessionId, agentName, @@ -856,7 +882,7 @@ async function createSessionRecord(event: SessionStartEvent, sessionId: string, activeDurationMs: 0, // Initialize active duration tracking ...(origin && { origin }), correlation: { - status: 'matched' as const, + status: correlationStatus, agentSessionId: event.session_id, agentSessionFile: event.transcript_path, retryCount: 0 diff --git a/src/providers/plugins/sso/session/SessionSyncer.ts b/src/providers/plugins/sso/session/SessionSyncer.ts index cce531fce..c1aed6380 100644 --- a/src/providers/plugins/sso/session/SessionSyncer.ts +++ b/src/providers/plugins/sso/session/SessionSyncer.ts @@ -112,6 +112,19 @@ export class SessionSyncer { } if (!sessionMetadata.correlation || sessionMetadata.correlation.status !== 'matched') { + // Handle 'file_not_found' status specially - this is expected for some interactive sessions + if (sessionMetadata.correlation?.status === 'file_not_found') { + logger.info( + `[SessionSyncer] Skipping session ${sessionId} - transcript file not found (status: file_not_found). ` + + `This is expected for some interactive PTY sessions where the transcript is not persisted.` + ); + return { + success: true, + message: 'Session skipped - transcript file not found (expected for some interactive sessions)', + processorResults: {}, + failedProcessors: [] + }; + } return { success: false, message: `Session not correlated (status: ${sessionMetadata.correlation?.status || 'unknown'})`,