{"record":{"id":"5d9664609ecfa801","repo":"conductor-oss/conductor","slug":"speech-api-returned-empty-body","errorCode":null,"errorMessage":"Speech API returned empty body","messagePattern":"Speech API returned empty body","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"ai/src/main/java/org/conductoross/conductor/ai/providers/openai/api/OpenAISpeechApi.java","lineNumber":86,"sourceCode":"        Request httpRequest =\n                new Request.Builder()\n                        .url(baseUrl + \"/audio/speech\")\n                        .header(authHeaderName, authHeaderValue)\n                        .header(\"Content-Type\", \"application/json\")\n                        .post(RequestBody.create(jsonBody, JSON))\n                        .build();\n\n        try (Response response = httpClient.newCall(httpRequest).execute()) {\n            if (!response.isSuccessful()) {\n                ResponseBody body = response.body();\n                String errorBody = body != null ? body.string() : \"\";\n                throw new IOException(\n                        \"Speech API failed with status %d: %s\"\n                                .formatted(response.code(), errorBody));\n            }\n            ResponseBody body = response.body();\n            if (body == null) {\n                throw new IOException(\"Speech API returned empty body\");\n            }\n            return body.bytes();\n        }\n    }\n\n    @JsonInclude(JsonInclude.Include.NON_NULL)\n    public record SpeechRequest(\n            String model,\n            String input,\n            String voice,\n            @JsonProperty(\"response_format\") String responseFormat,\n            Double speed) {}\n}\n","sourceCodeStart":68,"sourceCodeEnd":100,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/ai/src/main/java/org/conductoross/conductor/ai/providers/openai/api/OpenAISpeechApi.java#L68-L100","documentation":"Thrown by OpenAISpeechApi.createSpeech when the TTS request succeeds (2xx) but OkHttp's response.body() is null. This is a defensive guard rather than a normal OpenAI behavior, since a successful speech response always carries audio bytes.","triggerScenarios":"An intermediary (reverse proxy, WAF, corporate gateway, or load balancer) returns a 200 with no entity body, or OkHttp is configured with a response interceptor that consumes/nulls the body before createSpeech reads it.","commonSituations":"A misconfigured gateway rewriting the response, a buggy OkHttp interceptor calling body().string() earlier in the chain, or a transparent caching proxy serving an empty 200.","solutions":["Inspect any OkHttp interceptors configured on the shared httpClient for premature body consumption.","Bypass intermediate proxies and call OpenAI directly to confirm the body is non-empty.","If a gateway is required, ensure it streams the binary audio body through unchanged."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    byte[] audio = speechApi.createSpeech(req);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"empty body\")) {\n        // likely a proxy issue; retry once against the direct endpoint\n    } else {\n        throw e;\n    }\n}","preventionTips":["Do not install OkHttp interceptors that consume response bodies on the shared client.","Test against the direct OpenAI endpoint before routing through corporate proxies.","Ensure proxies forward binary audio bodies unchanged."],"tags":["openai","speech","tts","http","empty-body","proxy"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}