jamiepine/voicebox · error · OSError

Failed to save audio to {path}: {e}

Error message

Failed to save audio to {path}: {e}

What it means

Raised by save_audio() in backend/utils/audio.py when any exception occurs during the atomic write (mkdir, sf.write, or os.replace). The temp file is best-effort cleaned up and the original exception is chained. save_audio uses an atomic temp-then-rename strategy, so this typically indicates a filesystem-level failure rather than a partial/corrupt final file.

Source

Thrown at backend/utils/audio.py:110

        # Ensure parent directory exists
        Path(path).parent.mkdir(parents=True, exist_ok=True)

        # Write to temporary file first (explicit format since .tmp
        # extension is not recognised by soundfile)
        sf.write(temp_path, audio, sample_rate, format='WAV')

        # Atomic rename to final path
        os.replace(temp_path, path)

    except Exception as e:
        # Clean up temp file on failure
        try:
            if Path(temp_path).exists():
                Path(temp_path).unlink()
        except Exception:
            pass  # Best effort cleanup

        raise OSError(f"Failed to save audio to {path}: {e}") from e


def has_tts_runaway(
    audio: np.ndarray,
    sample_rate: int = 24000,
    frame_ms: int = 20,
    silence_threshold_db: float = -40.0,
    max_internal_silence_ms: int = 2000,
) -> bool:
    """Detect speech followed by a long silence and then more output.

    This shape is a reliable signal that a TTS model missed EOS and resumed
    with hallucinated speech or codec noise. Leading and trailing silence do
    not count because they are not bounded by non-silent audio.
    """
    frame_len = int(sample_rate * frame_ms / 1000)
    if frame_len == 0 or len(audio) < frame_len:
        return False

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Free disk space on the destination volume and retry.
  2. Check write permissions on Path(path).parent for the running process UID.
  3. Ensure the audio array is a float32 numpy array and sample_rate is a positive int before calling save_audio.
  4. Verify the storage mount is healthy (not read-only / disconnected).
  5. If the underlying error is a libsndfile encoding issue, convert the array dtype: audio = audio.astype(np.float32).

Example fix

// before: OSError because array is int16 and libsndfile rejects it
save_audio(audio, path, 24000)
// after: normalize dtype and confirm disk space
import numpy as np
audio = np.asarray(audio, dtype=np.float32)
save_audio(audio, path, 24000)
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import Path
import numpy as np

def can_save_audio(path: str, audio) -> bool:
    parent = Path(path).parent
    try:
        parent.mkdir(parents=True, exist_ok=True)
    except OSError:
        return False
    if not os.access(parent, os.W_OK):
        return False
    return isinstance(audio, np.ndarray) and audio.dtype == np.float32

Type guard

import numpy as np

def is_writable_audio_array(audio) -> bool:
    return isinstance(audio, np.ndarray) and audio.dtype == np.float32

Try / catch

try:
    save_audio(audio, path, sample_rate)
except OSError as e:
    logger.error('save_audio failed for %s: %s', path, e)
    raise HTTPException(507, f'Could not persist audio: {e}')

Prevention

When it happens

Trigger: Destination disk full; parent directory not writable (permissions); path on a read-only mount; soundfile/libsndfile cannot encode the array (dtype/shape mismatch); sample_rate invalid; path length / illegal characters causing OS errors during rename.

Common situations: Volume out of space during long synthesis runs; Docker container running as a UID without write permission to the mounted profiles dir; numpy array passed as int instead of float32 that libsndfile rejects; path on a network mount that dropped.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/4a1361cffdc1eb47. Report an issue: GitHub.