sipeed/picoclaw · error
failed to stat audio file %s: %w
Error message
failed to stat audio file %s: %w
What it means
Thrown when audioFile.Stat() fails right after a successful os.Open in WhisperTranscriber.Transcribe. Same rare class as the ElevenLabs stat error (303): the fd is open, so fstat fails only on a concurrently-closed handle, a broken network filesystem (stale NFS handle, EIO), or a kernel-level surprise. The size from this Stat feeds later logging and doRequest.
Source
Thrown at pkg/audio/asr/whisper_transcriber.go:148
return t.doRequest(ctx, &requestBody, writer.FormDataContentType(), int64(len(data)))
}
func (t *WhisperTranscriber) Transcribe(ctx context.Context, audioFilePath string) (*TranscriptionResponse, error) {
logger.InfoCF("voice", "Starting whisper transcription", map[string]any{
"audio_file": audioFilePath,
"model": t.modelID,
"provider": t.providerName,
})
audioFile, err := os.Open(audioFilePath)
if err != nil {
return nil, fmt.Errorf("failed to open audio file %s: %w", audioFilePath, err)
}
defer audioFile.Close()
fileInfo, err := audioFile.Stat()
if err != nil {
return nil, fmt.Errorf("failed to stat audio file %s: %w", audioFilePath, err)
}
var requestBody bytes.Buffer
writer := multipart.NewWriter(&requestBody)
part, err := writer.CreateFormFile("file", filepath.Base(audioFilePath))
if err != nil {
return nil, fmt.Errorf("failed to create form file: %w", err)
}
if _, copyErr := io.Copy(part, audioFile); copyErr != nil {
return nil, fmt.Errorf("failed to copy audio data: %w", copyErr)
}
if err = writer.WriteField("model", t.modelID); err != nil {
return nil, fmt.Errorf("failed to write model field: %w", err)
}
View on GitHub (pinned to 49183d7e8d)
Solutions
- Serialize cleanup and transcription (delete only after Transcribe returns).
- Copy network-mounted audio to local disk first.
- Run with -race to find concurrent Close of the same path.
Defensive patterns
Strategy: validation
Validate before calling
// The library Stats right after Open; do the same check first to own the error message:
fi, err := os.Stat(path)
if err != nil { return fmt.Errorf("audio unstable before transcription: %w", err) }
if fi.Size() == 0 { return errors.New("empty audio file") } Type guard
func isStatFailure(err error) bool {
var pe *os.PathError
return errors.As(err, &pe) && pe.Op == "stat"
} Try / catch
if _, err := whisper.Transcribe(ctx, path); err != nil {
if isStatFailure(err) {
// fd/fs-level inconsistency: serialize lifecycle, copy locally, retry once
return retryOnceAfterCopy(ctx, path)
}
return err
} Prevention
- One owner per audio file: no concurrent Close/Unlink during transcription.
- Copy audio off network mounts before transcribing.
- Test the voice pipeline with -race in CI.
When it happens
Trigger: Another goroutine closes or replaces the file between Open and Stat; NFS/FUSE mount returns an error for fstat; fd table corruption after an fd leak elsewhere.
Common situations: Race between temp-file cleanup and transcription start; containerized workloads on network volumes.
Related errors
- failed to get file info: %w
- failed to open audio file %s: %w
- failed to read audio file: %w
- failed to open audio file: %w
- failed to copy file content: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/704b244835192609.
Report an issue: GitHub.