Files
openchamber/packages/ui/src/sync/send-failure-log.ts
T
Bohdan Triapitsyn 2c52240f8e fix(sync): route sessions by server-confirmed directory, unstick queued sends
Session directory resolution had no precedence contract: the selection-time
directory short-circuited every lookup, and a persisted runtime value was
consulted before the authoritative record. A worktree session selected before
its directory store bootstrapped kept the active-directory fallback, and that
guess was persisted, so it survived reloads and restarts.

Directory resolution now lives in one module and orders sources by whether the
server confirmed the path, not by whether the value is local or synced:
authoritative (the child store that holds the session) > server-confirmed
selection > worktree attachment/metadata (the requested path, pre-canonical) >
remembered. A guessed selection is no longer persisted, remembered, or ranked.
Chips read the same resolution the composer used, so queue keys cannot diverge.

Queued auto-send could strand an item indefinitely: backoff, missing send
configuration, and the recent-abort window all returned without scheduling a
wake-up, so the queue only retried when an unrelated status or directory change
re-ran the effect. A retry scheduler now wakes it at the earliest known time.

A rejected send rolls the optimistic message back while the composer stays
silent for transport failures, which makes it indistinguishable from nothing
happening. Failures are now recorded to a bounded in-memory log surfaced in the
About diagnostics report, alongside a directory-resolution breakdown, plus
__opencodeDebug.diagnoseSessionDirectory() and getRecentSendFailures().

Prompted by a report of worktree prompting silently failing. That failure was
not reproduced locally, so the diagnostics are what will identify it.
2026-08-03 12:51:12 +03:00

50 lines
1.6 KiB
TypeScript

/**
* Recent prompt-send failures, kept in memory for diagnostics.
*
* A rejected send rolls the optimistic message back and, for transport-level
* failures, the composer stays silent by design. That makes a misrouted or
* refused prompt indistinguishable from "nothing happened" — the user has
* nothing to report beyond "it disappeared".
*
* This buffer gives the failure somewhere to live until someone asks for it,
* via the About dialog's diagnostics report or `__opencodeDebug`. It is
* in-memory only: never persisted, never sent anywhere, and dropped on reload.
*/
const MAX_RECORDED_SEND_FAILURES = 20
const MAX_REASON_LENGTH = 200
export type SendFailureRecord = {
at: number
sessionId: string
messageId: string
/** Directory the prompt was routed to — the value under suspicion. */
directory: string | null
/** HTTP status, or null for a transport failure with no response. */
status: number | null
/** Whether the send may still have been accepted server-side. */
ambiguous: boolean
/** Whether a confirmation refetch ran and failed to find the message. */
confirmationChecked: boolean
reason: string
}
const records: SendFailureRecord[] = []
export function recordSendFailure(record: Omit<SendFailureRecord, 'at' | 'reason'> & { reason: string }): void {
records.push({
...record,
reason: record.reason.slice(0, MAX_REASON_LENGTH),
at: Date.now(),
})
if (records.length > MAX_RECORDED_SEND_FAILURES) {
records.splice(0, records.length - MAX_RECORDED_SEND_FAILURES)
}
}
/** Newest first. */
export function getRecentSendFailures(): SendFailureRecord[] {
return [...records].reverse()
}