{"record":{"id":"dd910ab4625862ec","repo":"moeru-ai/airi","slug":"speech-engine-answered-endpoint-with-a-body-th","errorCode":null,"errorMessage":"Speech engine answered /${endpoint} with a body that is not JSON. Check that the Base URL points at a VOICEVOX-compatible engine.","messagePattern":"Speech engine answered /(.+?) with a body that is not JSON\\. Check that the Base URL points at a VOICEVOX-compatible engine\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/libs/providers/providers/voicevox/engine.ts","lineNumber":176,"sourceCode":"  const trimmed = baseUrl.trim()\n  return trimmed.endsWith('/') ? trimmed : `${trimmed}/`\n}\n\nfunction buildUrl(engineRequest: VoicevoxEngineRequest): URL {\n  const url = new URL(VOICEVOX_ENGINE_PATHS[engineRequest.endpoint], normalizeBaseUrl(engineRequest.baseUrl))\n  for (const [key, value] of Object.entries(engineRequest.query ?? {}))\n    url.searchParams.set(key, value)\n\n  return url\n}\n\nasync function decodeJson<T>(response: Response, endpoint: string): Promise<T> {\n  const body = await response.text()\n  try {\n    return JSON.parse(body) as T\n  }\n  catch {\n    throw new Error(`Speech engine answered /${endpoint} with a body that is not JSON. Check that the Base URL points at a VOICEVOX-compatible engine.`)\n  }\n}\n\nasync function request(\n  engineRequest: VoicevoxEngineRequest,\n  options?: VoicevoxEngineRequestOptions,\n): Promise<Response> {\n  let url: URL\n  try {\n    url = buildUrl(engineRequest)\n  }\n  catch {\n    throw new Error('The Base URL is not an absolute http:// or https:// address.')\n  }\n\n  const doFetch = options?.fetch ?? globalThis.fetch\n  const response = await doFetch(url, {\n    method: POST_ENDPOINTS.has(engineRequest.endpoint) ? 'POST' : 'GET',","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/libs/providers/providers/voicevox/engine.ts#L158-L194","documentation":"decodeJson reads the engine response as text and tries JSON.parse. VOICEVOX-family engines return JSON for their API endpoints, so a non-JSON body means the configured Base URL is not reaching a VOICEVOX-compatible engine (e.g. an HTML error page, login portal, or a different service). The library rethrows with a targeted message instead of surfacing the raw JSON.parse SyntaxError.","triggerScenarios":"Any engine endpoint call (e.g. /audio_query, /speakers) where the HTTP response body fails JSON.parse — HTML pages, plain-text proxy errors, empty bodies with content-type mismatch, or a captive portal response.","commonSituations":"Base URL typo pointing at a web page; a reverse proxy (nginx/Caddy) returning an HTML 502; pointing the URL at the VOICEVOX web UI instead of its API port; Docker port not actually mapped to the engine.","solutions":["Check the provider Base URL — it must point at the VOICEVOX engine API host:port (e.g. http://127.0.0.1:50021), not a webpage.","Curl the endpoint directly: `curl http://<base>/speakers` and confirm JSON output.","Inspect any reverse proxy logs for HTML error pages (502/503) being returned.","Confirm the engine container/process is running and its port is exposed."],"exampleFix":"// before\nbaseUrl: 'http://127.0.0.1:50021/dashboard'\n// after\nbaseUrl: 'http://127.0.0.1:50021'","handlingStrategy":"validation","validationCode":"const res = await fetch(`${baseUrl}/speakers`)\nconst text = await res.text()\ntry { JSON.parse(text) }\ncatch { throw new Error('Base URL does not point at a VOICEVOX-compatible engine') }","typeGuard":null,"tryCatchPattern":"try {\n  await synthesize(request)\n}\ncatch (error) {\n  if (errorMessageFrom(error).includes('not JSON'))\n    notifyUser('Check the VOICEVOX Base URL — the endpoint did not return JSON')\n  else\n    throw error\n}","preventionTips":["Store Base URL without trailing paths; point it at the engine API root.","Smoke-test the Base URL with `curl <base>/speakers` after configuring it.","Check proxy configs return JSON errors or pass engine responses through unmodified."],"tags":["network","configuration","voicevox","json"],"backgroundTag":"invalid-base-url","analyzedSha":"9c213115f8bd0fff9e6eabab02b077ac32da21be","analyzedAt":"2026-09-02T04:27:24.639Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}