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

  1. Retry the transcription once — transient read errors usually clear
  2. Read the file fully into memory right after validating it and use TranscribeData to remove the open-to-copy window
  3. Make temp-file cleanup age-based (only delete files older than e.g. 1h) so in-flight files survive
  4. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/07842c5615f118bd. Report an issue: GitHub.