moeru-ai/airi · warning · Error
Retry target has no retriable source message
Error message
Retry target has no retriable source message
What it means
retry() maps the target message to the user message that produced the turn using retrySourceIndexFrom; a negative result means no retriable source message exists for the requested index. Concretely, there is no user-role message at or before that position (or the index is out of bounds), so there is nothing to re-execute — e.g. retrying the opening greeting.
Source
Thrown at packages/stage-ui/src/stores/chat.ts:415
async function send(payload: ChatSendPayload): Promise<ChatSendResult> {
try {
return await executeSend(payload)
}
catch (error) {
appendSendError(payload.sessionId, error)
throw error
}
}
/** Replaces one stored turn with a new execution of its user message. */
async function retry(payload: ChatRetryPayload): Promise<ChatSendResult> {
if (!await chatSession.loadSession(payload.sessionId))
throw new Error('Failed to load the target chat session')
const currentMessages = chatSession.getSessionMessages(payload.sessionId)
const sourceIndex = retrySourceIndexFrom(currentMessages, payload.index)
if (sourceIndex < 0)
throw new Error('Retry target has no retriable source message')
const sourceMessage = currentMessages[sourceIndex]
const text = retryTextFrom(sourceMessage)
if (!text)
throw new Error('Retry target has no retriable user message')
chatSession.setSessionMessages(payload.sessionId, currentMessages.slice(0, sourceIndex))
try {
return await executeSend({
sessionId: payload.sessionId,
text,
tools: payload.tools ?? sourceMessage?.tools,
})
}
catch (error) {
appendSendError(payload.sessionId, error)
throw errorView on GitHub (pinned to 677329427f)
Solutions
- Only render retry affordances on assistant turns that have a preceding user message.
- Prefer addressing the retry target by stable message id rather than array index.
- Refresh messages and recompute indices immediately before retrying.
- If it still fires, treat it as a no-op in the UI instead of an error toast.
Example fix
// before
async function onRetry(index: number) {
await chatStore.retry({ sessionId, index })
}
// after
async function onRetry(index: number) {
const messages = chatSession.getSessionMessages(sessionId)
const hasUserSource = messages.slice(0, index + 1).some(m => m.role === 'user')
if (!hasUserSource)
return // nothing retriable (e.g. greeting or error stub)
await chatStore.retry({ sessionId, index })
} Defensive patterns
Strategy: validation
Validate before calling
const messages = chatSession.getSessionMessages(sessionId) const hasRetriableSource = messages .slice(0, payload.index + 1) .some(message => message.role === 'user' && message.content?.trim()) if (!hasRetriableSource) return // nothing to retry (greeting, error stub, or shifted index) await chatStore.retry(payload)
Type guard
function isRetriableMessageIndex(messages: ChatHistoryItem[], index: number): boolean {
if (index < 0 || index >= messages.length) return false
return messages.slice(0, index + 1).some(m => m.role === 'user' && Boolean(m.content?.trim()))
} Try / catch
try {
await chatStore.retry({ sessionId, index })
}
catch (error) {
if (errorMessageFrom(error) === 'Retry target has no retriable source message')
return // benign UI misuse: ignore
throw error
} Prevention
- Do not attach retry buttons to greetings or 'error' role stubs.
- Address retry targets by stable message id when the API allows it.
- Refresh the message list before computing retry indices.
- Treat this message as a no-op signal rather than an error toast.
When it happens
Trigger: Retrying the first assistant greeting (no preceding user message); retrying an 'error' role message appended by appendSendError; index-based retry after messages were deleted so indices shifted; passing an index past the end of the message list.
Common situations: Retry UI rendered on every message including greetings and error stubs; long histories where the user retries after manually deleting earlier turns; component state holding an old index after the list re-rendered.
Related errors
- Retry target has no retriable user message
- mutexAcquireTimeout must be a positive finite number
- timeout must be a positive finite number
- Tool input schema must be a JSON Schema object or a Standard
- Extension entrypoint id `${extension.id}` must match manifes
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/bdab075f5a1bb915.
Report an issue: GitHub.