{"record":{"id":"066163c914c744e4","repo":"can1357/oh-my-pi","slug":"options-label-failed-resp-status-detail","errorCode":null,"errorMessage":"${options.label} failed (${resp.status}): ${detail.slice(0, 300)}","messagePattern":"(.+?) failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":"ProviderHttpError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/tts.ts","lineNumber":132,"sourceCode":"\n\tlet response: Response;\n\ttry {\n\t\tresponse = await withAuth(\n\t\t\toptions.apiKey,\n\t\t\tasync key => {\n\t\t\t\tconst resp = await options.fetchImpl(options.url, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${key}`,\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\"User-Agent\": USER_AGENT,\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(options.payload),\n\t\t\t\t\tsignal: combinedSignal,\n\t\t\t\t});\n\t\t\t\tif (!resp.ok) {\n\t\t\t\t\tconst detail = await resp.text();\n\t\t\t\t\tthrow new ProviderHttpError(\n\t\t\t\t\t\t`${options.label} failed (${resp.status}): ${detail.slice(0, 300)}`,\n\t\t\t\t\t\tresp.status,\n\t\t\t\t\t\t{ headers: resp.headers },\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn resp;\n\t\t\t},\n\t\t\t{ signal: combinedSignal },\n\t\t);\n\t} catch (error) {\n\t\tconst status = (error as { status?: unknown }).status;\n\t\tif (error instanceof Error && typeof status === \"number\") {\n\t\t\treturn { errorText: error.message };\n\t\t}\n\t\tthrow error;\n\t}\n\treturn new Uint8Array(await response.arrayBuffer());\n}","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/tts.ts#L114-L150","documentation":"postSpeechRequest is the shared POST for cloud TTS providers (e.g. xAI, ElevenLabs). When the HTTP response is not ok, it reads the body (first 300 chars) and throws a ProviderHttpError carrying the status code and response headers. postSpeechRequest catches ProviderHttpError and converts it to { errorText } so the tool returns a friendly error result.","triggerScenarios":"The TTS provider returns any non-2xx status: 401 (invalid/expired API key), 400 (payload validation, voice_id not found, text too long), 402/429 (quota or rate limit), 500/503 (provider outage), 422 (unsupported language or output format combination).","commonSituations":"Expired or missing XAI_API_KEY / OAuth token; voice_id no longer offered by the provider; text exceeding provider character limits; rate limiting after a burst of syntheses; provider-side downtime; wrong region base URL.","solutions":["Read the embedded detail text — it contains the provider's own error message and status code","For 401/403, refresh credentials: re-run /login → xAI Grok OAuth or update XAI_API_KEY","For 429, wait and retry with backoff; for 400, check voice_id, language, text length, and output_format against provider limits","For 5xx, retry later or fall back to local TTS if configured","Note the 60 s AbortSignal.timeout fence: a timeout will surface as an abort, not this error"],"exampleFix":"// before\nawait synthesize({ text, voice_id: \"custom-voice-xyz\" })  // 400: voice not found\n// after\nconst voices = await provider.listVoices();\nawait synthesize({ text, voice_id: voices[0].id })  // use a valid voice id","handlingStrategy":"try-catch","validationCode":"// pre-flight checks before calling the tool\nif (!process.env.XAI_API_KEY && !hasOAuthToken()) throw new Error(\"No TTS credentials configured\");\nif (params.text.length > 4096) throw new Error(\"Text exceeds provider character limit\");","typeGuard":"function isProviderHttpError(e: unknown): e is ProviderHttpError {\n  return e instanceof ProviderHttpError || (e instanceof Error && typeof (e as { status?: unknown }).status === \"number\");\n}","tryCatchPattern":"const result = await ttsTool.run(params, ctx);\nif (result.isError) {\n  const text = result.content[0]?.text ?? \"\";\n  const statusMatch = text.match(/failed \\((\\d+)\\)/);\n  const status = statusMatch ? Number(statusMatch[1]) : 0;\n  if (status === 401 || status === 403) {\n    // prompt user to refresh credentials (re-login / XAI_API_KEY)\n  } else if (status === 429) {\n    // back off and retry later\n  } else if (status >= 500) {\n    // provider outage: retry with backoff or fall back to local TTS\n  }\n}","preventionTips":["Verify API credentials (XAI_API_KEY or OAuth) before invoking TTS","Respect provider limits: text length, valid voice_id, supported language/output_format","Add backoff/retry around 429 and 5xx statuses","Check the error's embedded detail text — it contains the provider's exact reason","Remember the 60 s timeout fence: long syntheses will abort rather than hang"],"tags":["http","tts","api","provider"],"backgroundTag":"provider-http-error","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}