From b1fa4ce487d6409f99ab876b5bfac1fc65e7e04c Mon Sep 17 00:00:00 2001 From: Neil Date: Sat, 19 Sep 2026 14:58:31 -0700 Subject: [PATCH] fix(native-chat): record why restart reconciliation leaves work unconfirmed Two silent paths hid the cause of an unconfirmed submission. The reconciler's bare `continue` on an `unknown` outcome dropped the reason it already carried, and the transcript read swallowed its error, collapsing an oversize file and a genuine read failure into the same verdict. Log both. No control flow changes. --- .../claude-structured-history-window.ts | 20 +++++++++++++++++-- .../journal-restart-reconciliation.ts | 6 ++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/claude/claude-structured-history-window.ts b/src/main/claude/claude-structured-history-window.ts index f7a541fa317..91ab691e195 100644 --- a/src/main/claude/claude-structured-history-window.ts +++ b/src/main/claude/claude-structured-history-window.ts @@ -13,7 +13,10 @@ // boundary rather than an empty window, because the two decide opposite things. import { join } from 'node:path' -import { readNodeFileWithinLimit } from '../../shared/node-bounded-file-reader' +import { + readNodeFileWithinLimit, + NodeFileReadTooLargeError +} from '../../shared/node-bounded-file-reader' import type { AgentSessionJournalIdentity } from '../../shared/agent-session-journal-types' import { resolveSessionFilePath } from '../native-chat/session-file-resolver' import type { @@ -289,7 +292,20 @@ export async function readClaudeProviderHistoryWindow(input: { MAX_HISTORY_WINDOW_SOURCE_BYTES ) contents = read.buffer.toString('utf8') - } catch { + } catch (error) { + // Both causes surface as the same INCONSISTENT verdict; only the log separates them. + console.warn( + '[claude-history-window] transcript unreadable; history treated as inconsistent:', + { + transcriptPath: input.transcriptPath, + sessionId: input.sessionId, + cause: + error instanceof NodeFileReadTooLargeError + ? `oversize: ${error.observedBytes} bytes exceeds the ${error.maxBytes} byte window budget` + : 'read failed', + error + } + ) return INCONSISTENT } return claudeProviderHistoryWindowFromJsonl({ ...input, contents }) diff --git a/src/main/native-chat/agent-session-journal/journal-restart-reconciliation.ts b/src/main/native-chat/agent-session-journal/journal-restart-reconciliation.ts index f699ce1293c..626baa9820a 100644 --- a/src/main/native-chat/agent-session-journal/journal-restart-reconciliation.ts +++ b/src/main/native-chat/agent-session-journal/journal-restart-reconciliation.ts @@ -91,6 +91,12 @@ export async function reconcileJournalSubmissionsAgainstHistory(input: { history: unseenHistory(input.journal, input.history) })) { if (outcome.outcome === 'unknown') { + // Narrowing failed: the submission stays unconfirmed, so record why. + console.warn('[journal-reconcile] submission left unconfirmed:', { + clientMessageId: outcome.clientMessageId, + reason: outcome.reason, + fence: input.fence + }) continue } await input.journal.resolveDispatch(