moeru-ai/airi · warning · Error
Retry target has no retriable user message
Error message
Retry target has no retriable user message
What it means
retry() found the source message via retrySourceIndexFrom, but retryTextFrom(sourceMessage) could not extract any user text from it, so there is no prompt to re-send. This happens when the located user message has empty or missing text content — for example an attachment-only turn with no caption, or a corrupted/incomplete history record.
Source
Thrown at packages/stage-ui/src/stores/chat.ts:420
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 error
}
}
/** Runs one stored tool call again and replaces its stored result. */
async function rerunToolCall(payload: ChatToolCallRerunPayload): Promise<void> {View on GitHub (pinned to 677329427f)
Solutions
- Disable retry for user turns whose text is empty (attachment-only messages).
- If the turn should have text, inspect the stored session record in IndexedDB and repair or drop the broken turn.
- Re-send the intended prompt as a new message instead of retrying.
- When migrating history formats, normalize user messages so text is present (or filter them out of retriable targets).
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 source = messages.slice(0, index + 1).reverse().find(m => m.role === 'user')
if (!source?.content?.trim()) {
toast.info('This turn has no text to retry')
return
}
await chatStore.retry({ sessionId, index })
} Defensive patterns
Strategy: validation
Validate before calling
const messages = chatSession.getSessionMessages(sessionId)
const source = messages.slice(0, payload.index + 1).reverse().find(m => m.role === 'user')
if (!source?.content?.trim()) {
toast.info('This turn has no text to retry')
return
}
await chatStore.retry(payload) Type guard
function hasRetriableUserText(messages: ChatHistoryItem[], index: number): boolean {
const source = messages.slice(0, index + 1).reverse().find(m => m.role === 'user')
return Boolean(source?.content?.trim())
} Try / catch
try {
await chatStore.retry({ sessionId, index })
}
catch (error) {
const message = errorMessageFrom(error) ?? ''
if (message === 'Retry target has no retriable user message') {
toast.info('Cannot retry a turn without text (e.g. attachments only)')
return
}
throw error
} Prevention
- Disable retry on user turns that contain only attachments.
- Normalize migrated histories so user messages always carry text.
- Validate imported session records before offering message actions.
- Educate users that media-only turns cannot be retried.
When it happens
Trigger: Retrying a user turn that consisted only of attachments (images/audio) with no text; a history record whose content field is empty due to an interrupted write or partial restore; message objects from an older schema where text lives under a different key.
Common situations: Users sending media-only messages then tapping retry; migrated or hand-edited chat histories with empty user turns; older record formats after a schema change in the session store.
Related errors
- Retry target has no retriable source 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/b9fa24dabbc6b09c.
Report an issue: GitHub.