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 FalseView on GitHub (pinned to 51f49dea19)
Solutions
- Free disk space on the destination volume and retry.
- Check write permissions on Path(path).parent for the running process UID.
- Ensure the audio array is a float32 numpy array and sample_rate is a positive int before calling save_audio.
- Verify the storage mount is healthy (not read-only / disconnected).
- 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
- Run the process under a UID with write permission to the storage volume.
- Monitor free disk space and alert before exhaustion during long synthesis runs.
- Normalize audio arrays to float32 before calling save_audio.
- Validate Path(path).parent is writable at startup.
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
- HTTP ${res.status}
- Audio file not found
- Sample audio not found for profile {profile_id}
- HTTP error! status: ${response.status}
- Navigator API is not available. This might be a Tauri config
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/4a1361cffdc1eb47.
Report an issue: GitHub.