Kokoro TTS half-close causing silent fallback to Groq #3

Open
opened 2026-04-20 08:42:51 +00:00 by fatmaebrahim · 4 comments
Member

Summary

Kokoro local TTS provider failing silently, causing all voice requests to fall back to Groq cloud TTS.

Root Cause

In hero_voice_tts_unix(), the code used tokio::io::split() to split the Unix stream, then called writer.shutdown() (sending a TCP FIN / half-close) before reading the response. The Kokoro server doesn't handle half-close properly — it sees the FIN and drops the connection, returning 0 bytes. This caused the Kokoro provider to always fail, silently falling back to Groq.

### Summary Kokoro local TTS provider failing silently, causing all voice requests to fall back to Groq cloud TTS. ### Root Cause In hero_voice_tts_unix(), the code used tokio::io::split() to split the Unix stream, then called writer.shutdown() (sending a TCP FIN / half-close) before reading the response. The Kokoro server doesn't handle half-close properly — it sees the FIN and drops the connection, returning 0 bytes. This caused the Kokoro provider to always fail, silently falling back to Groq. ### Related Issues: - https://forge.ourworld.tf/lhumina_code/hero_os/issues/77
Author
Member

Implementation Spec: Fix Kokoro TTS Half-Close Causing Silent Fallback to Groq

Objective

Fix a bug where the Kokoro TTS provider always failed silently due to a TCP half-close, causing every TTS request to fall back to the Groq cloud provider. Additionally, correct the default aibroker endpoint port and path, and reduce TTS text truncation length.

Requirements

  1. Eliminate the half-close that breaks Kokoro. The Unix socket write side must not be shut down before the response is read.
  2. Signal end-of-request via HTTP header. Use Connection: close instead of socket shutdown.
  3. Update default aibroker port from 9997 to 9988.
  4. Update voice TTS fallback URL path to include /ui.
  5. Reduce TTS text truncation limit from 3000 to 1000 characters.

File Modified

  • crates/hero_agent_server/src/routes.rs -- All changes are in this single file.

Implementation Plan

Step 1: Fix half-close in hero_voice_tts_unix()

  • Remove tokio::io::split(stream) -- use mut stream directly
  • Remove writer.shutdown() call (root cause)
  • Add Connection: close header to signal end of request
  • Read directly from stream instead of split reader

Step 2: Update default aibroker port

  • chat() function: 9997 -> 9988
  • voice_tts() function: 9997 -> 9988, plus append /ui to path

Step 3: Reduce TTS text truncation

  • Truncation threshold changed from 3000 to 1000 characters

Acceptance Criteria

  • Kokoro TTS returns audio without silent fallback to Groq
  • No writer.shutdown() call in hero_voice_tts_unix()
  • Connection: close header present in Unix socket HTTP request
  • Default aibroker port is 9988 in chat() and voice_tts()
  • Voice TTS fallback URL includes /ui path
  • TTS text truncated at 1000 characters
  • Existing Groq fallback still works when Kokoro is unavailable
## Implementation Spec: Fix Kokoro TTS Half-Close Causing Silent Fallback to Groq ### Objective Fix a bug where the Kokoro TTS provider always failed silently due to a TCP half-close, causing every TTS request to fall back to the Groq cloud provider. Additionally, correct the default aibroker endpoint port and path, and reduce TTS text truncation length. ### Requirements 1. Eliminate the half-close that breaks Kokoro. The Unix socket write side must not be shut down before the response is read. 2. Signal end-of-request via HTTP header. Use `Connection: close` instead of socket shutdown. 3. Update default aibroker port from 9997 to 9988. 4. Update voice TTS fallback URL path to include `/ui`. 5. Reduce TTS text truncation limit from 3000 to 1000 characters. ### File Modified - `crates/hero_agent_server/src/routes.rs` -- All changes are in this single file. ### Implementation Plan #### Step 1: Fix half-close in `hero_voice_tts_unix()` - Remove `tokio::io::split(stream)` -- use `mut stream` directly - Remove `writer.shutdown()` call (root cause) - Add `Connection: close` header to signal end of request - Read directly from `stream` instead of split reader #### Step 2: Update default aibroker port - `chat()` function: 9997 -> 9988 - `voice_tts()` function: 9997 -> 9988, plus append `/ui` to path #### Step 3: Reduce TTS text truncation - Truncation threshold changed from 3000 to 1000 characters ### Acceptance Criteria - [ ] Kokoro TTS returns audio without silent fallback to Groq - [ ] No `writer.shutdown()` call in `hero_voice_tts_unix()` - [ ] `Connection: close` header present in Unix socket HTTP request - [ ] Default aibroker port is 9988 in `chat()` and `voice_tts()` - [ ] Voice TTS fallback URL includes `/ui` path - [ ] TTS text truncated at 1000 characters - [ ] Existing Groq fallback still works when Kokoro is unavailable
Author
Member

Test Results

  • Total: 86
  • Passed: 86
  • Failed: 0

All tests passed across all crates:

  • hero_agent (lib): 76 passed
  • hero_agent_examples (integration): 2 passed
  • hero_agent_server: 8 passed
  • All other crates (hero_agent_sdk, hero_agent_tools, hero_agent_ui, doc-tests): 0 tests (no test functions defined)

Note: 14 compiler warnings present (unused imports, unused variables, dead code) but no errors.

## Test Results - Total: 86 - Passed: 86 - Failed: 0 All tests passed across all crates: - hero_agent (lib): 76 passed - hero_agent_examples (integration): 2 passed - hero_agent_server: 8 passed - All other crates (hero_agent_sdk, hero_agent_tools, hero_agent_ui, doc-tests): 0 tests (no test functions defined) Note: 14 compiler warnings present (unused imports, unused variables, dead code) but no errors.
Author
Member

Implementation Summary

Changes Made

All changes in crates/hero_agent_server/src/routes.rs:

  1. Fixed half-close in hero_voice_tts_unix() (root cause)

    • Removed tokio::io::split(stream) -- now uses mut stream directly
    • Removed writer.shutdown() call that sent TCP FIN, causing Kokoro to drop the connection
    • Added Connection: close header to signal end-of-request via HTTP instead
    • Changed reads from split reader to direct stream
  2. Updated default aibroker port from 9997 to 9988 in both chat() and voice_tts()

  3. Updated voice TTS fallback URL to include /ui path segment

  4. Reduced TTS text truncation from 3000 to 1000 characters

Test Results

  • 86 tests passed, 0 failures
  • All existing functionality preserved

Notes

  • The Groq fallback path remains functional for cases where Kokoro is genuinely unavailable
  • The fix is minimal and targeted -- only the Unix socket communication pattern was changed
## Implementation Summary ### Changes Made All changes in `crates/hero_agent_server/src/routes.rs`: 1. **Fixed half-close in `hero_voice_tts_unix()`** (root cause) - Removed `tokio::io::split(stream)` -- now uses `mut stream` directly - Removed `writer.shutdown()` call that sent TCP FIN, causing Kokoro to drop the connection - Added `Connection: close` header to signal end-of-request via HTTP instead - Changed reads from split `reader` to direct `stream` 2. **Updated default aibroker port** from 9997 to 9988 in both `chat()` and `voice_tts()` 3. **Updated voice TTS fallback URL** to include `/ui` path segment 4. **Reduced TTS text truncation** from 3000 to 1000 characters ### Test Results - 86 tests passed, 0 failures - All existing functionality preserved ### Notes - The Groq fallback path remains functional for cases where Kokoro is genuinely unavailable - The fix is minimal and targeted -- only the Unix socket communication pattern was changed
Author
Member

Pull request opened: #4

This PR implements the changes discussed in this issue.

Pull request opened: https://forge.ourworld.tf/lhumina_code/hero_agent/pulls/4 This PR implements the changes discussed in this issue.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_agent#3
No description provided.