moeru-ai/airi · error · Error
No voice selected. Pick a character in the speech settings.
Error message
No voice selected. Pick a character in the speech settings.
What it means
The VOICEVOX provider validates the incoming speech request body before proxying it to the engine. If the parsed JSON body has no `voice` field (empty string, missing, or null), readSpeechRequest throws this error because the engine needs a speaker/style id to synthesize audio. The library throws it early so the user gets an actionable message instead of an opaque engine-side 400.
Source
Thrown at packages/stage-ui/src/libs/providers/providers/voicevox/define.ts:211
})
}
/**
* Reads the segment text and the style id out of the OpenAI-shaped body that
* `generateSpeech` builds.
*
* `requestBody` in `@xsai/shared` passes that object through `objCamelToSnake`.
* Only single-word keys survive unchanged, and `input` and `voice` are two of
* them. A key of more than one word arrives renamed. The synthesis parameters
* therefore come from the provider configuration, not from this body.
*/
function readSpeechRequest(init: RequestInit | undefined): { input: string, voice: string } {
if (!init?.body || typeof init.body !== 'string')
throw new Error('Invalid speech request body')
const body = JSON.parse(init.body) as { input?: string, voice?: string }
if (!body.voice)
throw new Error('No voice selected. Pick a character in the speech settings.')
return { input: body.input ?? '', voice: body.voice }
}
function toVoiceInfo(providerId: string, speakerName: string, style: { id: number, name: string }): VoiceInfo {
return {
id: String(style.id),
languages: [{ code: 'ja', title: 'Japanese' }],
name: `${speakerName} / ${style.name}`,
provider: providerId,
}
}
View on GitHub (pinned to 9c213115f8)
Solutions
- Open the speech settings and select a character (speaker/style) so a voice id is persisted.
- Verify the settings store actually saves the selected voice id (check providers/voicevox settings state after selection).
- When calling the provider programmatically, always include `voice` in the JSON body: `{ input, voice }`.
- Add UI-level validation that disables the speak action until a voice is selected.
Example fix
// before
await fetch('/api/voicevox/speech', { method: 'POST', body: JSON.stringify({ input: 'hi' }) })
// after
await fetch('/api/voicevox/speech', { method: 'POST', body: JSON.stringify({ input: 'hi', voice: selectedVoiceId }) }) Defensive patterns
Strategy: validation
Validate before calling
if (!selectedVoiceId) {
openSpeechSettings()
return
}
await sendSpeech({ input, voice: selectedVoiceId }) Prevention
- Always read the voice id from the settings store immediately before sending a speech request.
- Disable the speak action in the UI until a character/style is selected.
- Persist the voice selection and validate it on app startup.
When it happens
Trigger: Calling the provider's speech endpoint (fetch to the provider route) with a JSON body where `voice` is absent, empty string, or null; e.g. `{ "input": "hello" }` with no voice field.
Common situations: The user never picked a character/style in the speech settings so the stored voice id is empty; a settings migration or fresh install left the voice field unset; custom UI code invoking the TTS route omits `voice`.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- MiMo voice clone requires a base64 audio sample in data URI
- MiMo voice design requires a style prompt in the user messag
- Invalid speech request body
- The Base URL is not an absolute http:// or https:// address.
- Unknown Kokoro voice: ${body.voice}
AI-assisted analysis of moeru-ai/airi@9c213115f8 (2026-09-02).
Data as JSON: /api/errors/1f3d92cfb1135226.
Report an issue: GitHub.