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.
This commit is contained in:
Neil
2026-09-19 14:58:31 -07:00
parent f59ada2703
commit b1fa4ce487
2 changed files with 24 additions and 2 deletions
@@ -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 })
@@ -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(