sipeed/picoclaw · error
failed to copy audio data: %w
Error message
failed to copy audio data: %w
What it means
Returned by WhisperTranscriber.Transcribe when io.Copy fails while streaming the opened audio file into the multipart part. The destination is an in-memory bytes.Buffer that does not error, so the failure comes from reading audioFile itself: I/O errors on disk or network mounts, or the file being truncated/deleted between os.Open and the copy. Wrapped with %w.
Source
Thrown at pkg/audio/asr/whisper_transcriber.go:160
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)
}
if err = writer.WriteField("response_format", "json"); err != nil {
return nil, fmt.Errorf("failed to write response_format field: %w", err)
}
if err = writer.Close(); err != nil {
return nil, fmt.Errorf("failed to close multipart writer: %w", err)
}
return t.doRequest(ctx, &requestBody, writer.FormDataContentType(), fileInfo.Size())
}
func (t *WhisperTranscriber) doRequest(View on GitHub (pinned to 49183d7e8d)
Solutions
- Retry the transcription once — transient read errors usually clear
- Read the file fully into memory right after validating it and use TranscribeData to remove the open-to-copy window
- Make temp-file cleanup age-based (only delete files older than e.g. 1h) so in-flight files survive
- Check disk and mount health if errors persist
Example fix
// before
resp, err := transcriber.Transcribe(ctx, audioPath)
// after
data, err := os.ReadFile(audioPath)
if err != nil {
return nil, fmt.Errorf(`read audio: %w`, err)
}
resp, err := transcriber.TranscribeData(ctx, data, filepath.Base(audioPath)) Defensive patterns
Strategy: retry
Validate before calling
data, err := os.ReadFile(audioPath)
if err != nil {
return err
}
if len(data) == 0 {
return errors.New(`refusing to transcribe empty audio file`)
}
// pass data to TranscribeData to remove the open-to-copy race window Type guard
func isPathReadFailure(err error) bool {
var perr *fs.PathError
return errors.As(err, &perr)
} Try / catch
if err != nil {
var perr *fs.PathError
if errors.As(err, &perr) {
// file vanished or read failed mid-copy: re-read once (or re-record), then surface
}
} Prevention
- Delete temp audio by age (e.g. older than 1h), never in-flight files
- Read the whole file up front (os.ReadFile) and use TranscribeData
- Log file size at open and bytes copied to detect truncation races
When it happens
Trigger: The audio temp file is deleted by a concurrent cleanup routine after os.Open but before or during io.Copy; reading from an NFS/FUSE mount that returns EIO/ESTALE; a truncated or zero-byte recording; hardware read errors.
Common situations: Voice pipelines where the temp WAV/OGG is removed by a race with the recorder; files on network storage; long-lived processes where the file disappears between open and upload.
Related errors
- close destination file %s: %w
- failed to copy file content: %w
- failed to open audio file %s: %w
- failed to stat audio file %s: %w
- failed to create request: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/07842c5615f118bd.
Report an issue: GitHub.