Troubleshooting
Diagnose SDK 0.18.0 connection, mode, audio, finality, result, TTS, rate-limit, and cleanup failures.
This page targets @humain-voice/sdk@0.18.0 and
humain-voice==0.18.0. Diagnose from evidence: capture the status, event,
identifier, and final signal before changing configuration or retrying.
Diagnose in this order
- Confirm the installed package is exactly
0.18.0. - Confirm
API_URLandAPI_KEYare present in the server process. - Choose the processing mode from the input you actually have.
- Reproduce with one small, known input and one request. Turn off concurrent retries while isolating the failure.
- Record structured error fields and whether cleanup completed.
Run only the package-version command relevant to your app. The shell
loop reports presence without printing the secret; do not replace it with
env or another command that exposes API_KEY.
npm ls @humain-voice/sdk --depth=0
python -c 'from importlib.metadata import version; print(version("humain-voice"))'
for name in API_URL API_KEY; do
if [ -n "$(printenv "$name")" ]; then
printf "%s=set\n" "$name"
else
printf "%s=missing\n" "$name"
fi
doneChoose the correct processing mode
| Symptom | Likely cause | Evidence or check | Fix |
|---|---|---|---|
| A long meeting, podcast, or archive file stalls in fast transcription | Fast transcription was used for long-form media | The entire recording existed before the request and is long-form | Use BatchTranscribeClient; poll under a finite deadline |
| A bounded conversational turn has unnecessary streaming complexity | Realtime was used although the complete turn already exists | No audio arrives after the request starts | Use FastTranscriptionClient for the already-complete, latency-sensitive unit |
| A microphone or call is repeatedly uploaded as completed files | Batch or fast mode was used while audio is still arriving | Processing must begin before recording ends | Use RealtimeClient or RealtimeDiarizationClient and send framed PCM as it arrives |
| A complete file is sent as realtime PCM, or PCM is uploaded as a file | Container and stream inputs were confused | Compare the input bytes with the selected operation's contract | Send an encoded file to batch or fast; send headerless PCM16 LE to realtime |
Mode selection does not promise a particular latency. It selects the lifecycle and input contract that match the task.
Connection and authentication symptoms
| Symptom | Likely cause | Evidence or check | Fix |
|---|---|---|---|
REST returns 401 or 403 | The key is missing, invalid, or lacks access to the operation | Record the HTTP status and structured code; confirm only that API_KEY is set | Send the provisioned value as x-api-key or api_key; resolve invalid or access-denied credentials through your organization's approved access flow |
| A Socket.IO constructor says URL or key is required | api_url or api_key is empty | Log the option names and presence, never the key value | Supply the provisioned API_URL and API_KEY; api_path defaults to /socket.io |
Socket.IO raises connect_error or never calls the connection handler | The host or path is wrong, the WebSocket upgrade is blocked, or the handshake is rejected | Compare API_URL and the effective /socket.io path with the issued values; in Python, reproduce once with verbose=True and retain the handshake error | Use the default path unless your deployment documents an override, keep WebSocket transport enabled, and configure the proxy to preserve the upgrade |
| REST works but every Socket.IO capability fails | The Socket.IO route or WebSocket upgrade is blocked | A protected REST call succeeds while the Socket.IO handshake fails before any app event | Test /socket.io from the same server network; set API_PATH only for a documented override |
| A Socket.IO handshake is rejected before any app event | Its required Origin header is missing or does not match the service origin | Compare the sanitized handshake headers; the body can be a gateway page rather than a structured platform error | Set Origin to the scheme and host of API_URL. A direct Socket.IO client must set it; SDK 0.18.0 derives it from api_url. |
| A hand-built Socket.IO client connects differently from the SDK | The path, API-key header, or transport differs | Inspect the sanitized handshake: path, transport, and x-api-key presence | Send x-api-key, select transports: ["websocket"], and register handlers before connecting |
Release 0.18.0 defaults Socket.IO to /socket.io; use API_PATH only for a
documented override. The legacy sautech.humain.com endpoint requires
/realtime/socket.io. Keep credentials in a server-side process; moving a key
into a browser or mobile bundle is not a connection fix.
Audio and framing symptoms
Inspect an encoded source, then create the exact raw realtime input when needed:
ffprobe -v error -select_streams a:0 \
-show_entries stream=codec_name,sample_rate,channels,sample_fmt \
-of default=noprint_wrappers=1 input.wav
ffmpeg -i input.wav -ar 16000 -ac 1 -c:a pcm_s16le \
-f s16le realtime.pcm| Symptom | Likely cause | Evidence or check | Fix |
|---|---|---|---|
Batch returns 422 with VALIDATION_FILE_CORRUPT | The uploaded file is corrupt or unsupported | Run ffprobe; retain the status, code, and safe file metadata | Decode or transcode to a valid encoded audio file, then retry once as a new submission |
| Fast transcription accepts an upload but produces no useful final result | The complete payload uses an unsupported container or malformed MP4 | Confirm it is AAC, FLAC, MP3, MP4, or WAV; inspect MP4 layout | Send one complete supported file; place the MP4 moov atom at the front |
| Realtime text is empty, garbled, too fast, or too slow | A WAV/MP3 container, big-endian samples, wrong sample rate, or wrong channel count was sent as PCM | ffprobe the source and inspect the conversion command; PCM payload length must be even | Send headerless PCM16 little-endian, 16 kHz, mono bytes |
| A direct realtime client receives nothing | The 18-byte application header, UUID, flags, or language byte is wrong | Inspect bytes 0..17; verify one UUID is reused and audio starts at byte 18 | For audio_stream, send flags 1 once, 0 between, and 2 once at the end; use the documented language byte |
| Direct live diarization never finalizes | diarization_stream framing or the final flag is missing | Verify the same 18-byte header, one UUID, start bit, and final bit | Send PCM16 LE at 16 kHz mono and exactly one final frame; keep the other flag bits zero |
| Updates arrive in an uneven cadence | Payload sizes differ substantially from the tested helpers | Count audio bytes after the 18-byte header | Start with 3,200 audio bytes per realtime ASR frame; the SDK recommends 15,360 bytes per live diarization feed |
The frame sizes are tested cadences, not throughput or latency guarantees. The SDK constructs headers; inspect them only for a direct wire implementation.
Language and model symptoms
| Symptom | Likely cause | Evidence or check | Fix |
|---|---|---|---|
| Arabic-English speech is recognized as one language | Language and model do not describe code-switching | Log the exact enum values, not only their labels | Use Language.ArEn with BatchTranscriptionModel.BayanArEn or FastTranscriptionModel.BayanArEn |
| Fast transcription is empty with an 8 kHz telephony model | A batch-only model was forced into the fast pipeline | NidaArTelephony is absent from FastTranscriptionModel in 0.18.0 | Use BatchTranscriptionModel.NidaArTelephony with batch; do not pass its wire string to fast |
| An unexpected language behaves as Arabic | An unrecognized string reached the protocol converter | Log the exact value passed to the SDK; unknown strings map to protocol ID 0 in 0.18.0 | Pass Language.Ar, Language.En, or Language.ArEn instead of a free-form label |
| Realtime configuration includes a batch or fast ASR model | Realtime was treated like a file pipeline | Type-check the call; RealtimeClient.startStream() / start_stream() selects a language, not an ASR model | Remove the model option and pass the correct Language value |
| Direct HTTP TTS fails when no model is specified | Only the direct route leaves model selection to the deployment, whose configured default can differ or be unavailable. The SDK always sends nebula when you omit model, so this cannot occur through TTSClient | Record the structured error event and the request payload without text if it is sensitive | Send the explicit model key nebula on the direct route instead of relying on deployment configuration |
Use the unversioned BayanArEn alias for the released code-switching default.
Choose BayanArEnV1 or BayanArEnV2 only when you intentionally require that
specific model. See Models and languages.
Missing final signals and deadline symptoms
| Symptom | Likely cause | Evidence or check | Fix |
|---|---|---|---|
| A batch job never returns from the helper | It remains non-terminal, becomes cleared, or exceeds the helper deadline | Log every status: queued, processing, done, failed, or cleared | Use a finite poll deadline; stop on done, failed, or cleared; call getResult() / get_result() directly when immediate cleared handling is required |
| Fast upload acknowledgement arrives but the request never completes | audio_file_upload_success was mistaken for transcription completion | Match its id, then check for transcription_result.is_final === true | Wait only under an app deadline; Python defaults timeout_seconds to 60, while JavaScript 0.18.0 has no fast-request timeout option |
RealtimeStream.close() / close() returns without protocol is_final | Its bounded protocol-final wait expired | Track wire-terminal is_final; the default close wait is one second | is_speech_final is only an utterance boundary and does not satisfy the SDK helper. Preserve confirmed text and mark the result incomplete. |
Diarization close() returns a timeline without a final update | Its five-second close wait expired | Track the last update's isFinal / is_final; the returned timeline is the best-known snapshot | Mark it incomplete unless finality was observed; retain the reconciled snapshot and disconnect |
| TTS times out between chunks or never emits its final chunk | The per-chunk inactivity wait expired, or byte 16 bit 0 never arrived | Record the time of each tts_audio frame and its is_last value | Bound per-chunk inactivity and the whole synthesis separately; JavaScript defaults each chunk wait to 30 seconds, while Python has no default |
An SDK timeout and an application deadline are different. The SDK timeout may bound a poll, close wait, or next chunk. Your application deadline must bound the entire operation, including connection, work, finality, and retries. A timeout never proves that an upload failed or a stream finalized.
Subtitle and diarization result symptoms
| Symptom | Likely cause | Evidence or check | Fix |
|---|---|---|---|
| Live captions repeat provisional text | Every transcription_result was appended | Log id, arrival order, seq, is_final, and is_speech_final | Keep one replaceable provisional line per id; commit only final or speech-final event words |
RealtimeSubtitles keeps only one of several final events | The helper deduplicates by id:seq, but the current wire contract does not guarantee distinct seq values | Compare final-event count and seq values with RealtimeSubtitles.words | Collect final-event words in observed arrival order and render them with Subtitles after termination |
| Speaker turns repeat, disappear, or jump | Raw final_segments and active_segments were concatenated | Compare consecutive raw arrays with update.segments | Accumulate unseen finalized segments, replace the active tail, sort by start time, or consume the SDK's reconciled update.segments |
| Batch words have no speaker even though diarization exists | Word and diarization timelines are separate in the response shape | Inspect final_word_segments / word offsets and diarization_segments | Reconcile by temporal overlap and define an app rule for gaps or ambiguous overlap; do not invent a speaker silently |
RealtimeSubtitles deliberately ignores provisional responses and deduplicates
final responses by id and seq; that exact behavior is why it can collapse
distinct final events under the current wire contract. Live diarization's
active_segments remain revisable until they move into finalized state.
TTS voice and playback symptoms
| Symptom | Likely cause | Evidence or check | Fix |
|---|---|---|---|
listVoices() / list_voices() returns [] | One or more physical variants needed by the configured profiles are unavailable | Record the array length and any structured error; do not index element 0 | Handle the empty state and do not guess a voice_id; retry only under a bounded policy |
| Voice discovery waits forever in Python or times out in JavaScript | Timeout defaults differ | JavaScript defaults to five seconds; Python uses no default | Pass listVoices({ timeoutSeconds: 5 }) or list_voices(timeout_seconds=5) explicitly |
| Synthesized bytes do not play in a media player | Socket.IO TTS returns raw PCM, not a WAV file | Confirm the response reached is_last; inspect the byte count | Treat bytes as PCM16 LE, 24 kHz, mono and add a correct WAV header with the tested TTS-to-WAV recipe |
| JavaScript WAV output is truncated or contains unrelated bytes | A Uint8Array view was converted without its offset and length | Compare byteLength with the resulting Buffer.length | Construct the Buffer with the view's byteOffset and byteLength |
Code matching on a previously-seen label string stops finding a voice | The catalog exposes profile labels such as mul_<name> rather than physical variant labels | Inspect the returned profile metadata | Match and persist the profile id, never label; physical variant IDs are rejected |
| A multilingual profile selects the unexpected physical variant | Arabic routing requires an Arabic-script letter in text | Check the text for an Arabic-script letter | Any Arabic-script letter selects Arabic; otherwise English is selected |
For direct Socket.IO parsing, each tts_audio payload starts with a 16-byte
UUID and one header byte. Append only bytes 17..end; byte 16 bit 0 is the
final signal.
Rate-limit and retry symptoms
| Symptom | Likely cause | Evidence or check | Fix |
|---|---|---|---|
Batch raises BatchTranscribeRateLimitError or returns 429 | Audio capacity is temporarily unavailable for the request | Inspect retryAfter / retry_after, capacity, retryable, and code when present | Honor a supplied delay, add exponential backoff with jitter, and cap attempts and total elapsed time |
maxRetries / max_retries appears to do nothing | It is deprecated and ignored in 0.18.0 | The SDK emits a deprecation warning for a nonzero value | Implement the bounded retry policy in application code |
| A connection fails after an upload was sent | The outcome is ambiguous | Record whether a job ID or upload acknowledgement was received | Do not blindly upload again; the API publishes no idempotency-key contract, so apply an app duplication policy or escalate with the evidence |
A Socket.IO error says retryable: true | The server classified the event as retryable, not guaranteed to succeed | Capture id, code, message, retryable, and timestamp from onError / on_error | Use that field as one input to the same bounded policy; do not loop indefinitely |
capacity is an operational response field, not a published account quota or
availability guarantee. A Batch status read is repeatable only when
save_result=true preserved terminal output before retrieval; the default read
can clear it. An upload whose outcome is unknown remains unsafe to replay. See
Errors and rate limits.
Cleanup and leaked-connection symptoms
| Symptom | Likely cause | Evidence or check | Fix |
|---|---|---|---|
| The process stays alive after work completes | A Socket.IO client or Python HTTP session remains open | Log client creation, final signal, and cleanup once per operation; Python may report an unclosed session | Put cleanup in finally; call FastTranscriptionClient.close(), TTSClient.close(), RealtimeClient.disconnect(), or RealtimeDiarizationClient.disconnect() as appropriate |
| Connections increase after errors or timeouts | A new client is created before the failed one is closed | Compare connection and disconnection callback counts | Reuse one healthy client where appropriate and close the failed client before retrying |
| A stream ends without cleanup | The result loop exited before stream.close() | Record whether the final input and close path ran | Close the stream in finally, then disconnect the client if the failure occurred outside normal stream cleanup |
Python batch warns about an unclosed aiohttp session | BatchTranscribeClient.close() / close_sync() was skipped | Reproduce one request and observe process shutdown | Use the async or sync context manager, or call the matching close method in finally |
JavaScript BatchTranscribeClient.close() is a compatibility no-op in
0.18.0; its requests use fetch. The other JavaScript clients own Socket.IO
connections and require their documented cleanup paths.
Escalate with reproducible evidence
Retry one known input only when the outcome is unambiguous and the policy allows it. If the problem remains, send your HUMAIN contact a minimal reproduction and this sanitized record:
sdk: "@humain-voice/sdk@0.18.0 | humain-voice==0.18.0"
operation: "batch | fast | realtime | diarization | tts | voice-list"
api_url_host: "host only"
api_path: "Socket.IO path or not-applicable"
started_at_utc: "ISO-8601 timestamp"
request_or_job_id: "UUID if available"
input: "codec, sample_rate, channels, duration, byte_count"
observed: "http_status, event, final_signal"
error: "code, message, retryable, timestamp"
retries: "count and delays"
cleanup: "final frame, stream close, client disconnect"Attach the smallest code sample that reproduces the issue and state the expected final signal. Do not send the API key, a full Socket.IO URL containing its query string, or sensitive audio/text without authorization. For an ambiguous upload, include its UTC window, safe input checksum, and any job or request ID instead of submitting it again.
These docs do not publish an outage-status URL, retention duration, quota, availability target, or support response-time guarantee. Escalate credential and access-scope failures to the key issuer; escalate repeatable protocol or finality failures with the evidence above.
Authentication
Check key handling, access, and server-side credential safety.
Socket.IO lifecycle
Inspect exact prerequisites, event sequences, framing, and final signals.
Errors and rate limits
Classify structured errors and apply bounded retries.
Models and languages
Select a language and a pipeline-supported model.