sipeed/picoclaw · error

failed to copy file content: %w

Error message

failed to copy file content: %w

What it means

Thrown when io.Copy(part, audioFile) fails while streaming the opened audio file into the multipart buffer in ElevenLabsTranscriber.Transcribe. This is a read error on the file descriptor mid-copy (the multipart side is an in-memory buffer that does not fail under normal conditions). The wrapped error is usually a *os.PathError with Op 'read'.

Source

Thrown at pkg/audio/asr/elevenlabs_transcriber.go:79

	}

	logger.DebugCF("voice", "Audio file details", map[string]any{
		"size_bytes": fileInfo.Size(),
		"file_name":  filepath.Base(audioFilePath),
	})

	var requestBody bytes.Buffer
	writer := multipart.NewWriter(&requestBody)

	part, err := writer.CreateFormFile("file", filepath.Base(audioFilePath))
	if err != nil {
		logger.ErrorCF("voice", "Failed to create form file", map[string]any{"error": err})
		return nil, fmt.Errorf("failed to create form file: %w", err)
	}

	if _, err = io.Copy(part, audioFile); err != nil {
		logger.ErrorCF("voice", "Failed to copy file content", map[string]any{"error": err})
		return nil, fmt.Errorf("failed to copy file content: %w", err)
	}

	if err = writer.WriteField("model_id", t.modelID); err != nil {
		return nil, fmt.Errorf("failed to write model_id field: %w", err)
	}

	if err = writer.Close(); err != nil {
		logger.ErrorCF("voice", "Failed to close multipart writer", map[string]any{"error": err})
		return nil, fmt.Errorf("failed to close multipart writer: %w", err)
	}

	url := t.apiBase + "/v1/speech-to-text"
	req, err := http.NewRequestWithContext(ctx, "POST", url, &requestBody)
	if err != nil {
		logger.ErrorCF("voice", "Failed to create request", map[string]any{"error": err})
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Make sure the recording is fully written and closed before Transcribe is called (write to temp name, rename atomically).
  2. If reading from a mount, copy the file locally first (os.ReadFile then transcribe from a temp file).
  3. Check dmesg/system logs for disk errors if EIO recurs.

Example fix

// before: transcribe while recorder may still write
resp, err := t.Transcribe(ctx, livePath)

// after: atomic handoff via rename
tmp := livePath + ".part"
// recorder writes and closes tmp, then:
if err := os.Rename(tmp, livePath); err != nil { return err }
resp, err := t.Transcribe(ctx, livePath)
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the file is fully written and stable before transcription:
fi, err := os.Stat(path)
if err != nil { return err }
if fi.Size() == 0 { return errors.New("audio still being written") }

Type guard

func isReadErr(err error) bool {
    var pe *os.PathError
    return errors.As(err, &pe) && pe.Op == "read"
}

Try / catch

resp, err := el.Transcribe(ctx, path)
if err != nil {
    if isReadErr(err) {
        // mid-copy I/O failure: verify the file then retry once
        if _, serr := os.Stat(path); serr == nil {
            resp, err = el.Transcribe(ctx, path)
        }
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: The file is truncated or deleted-and-recreated while being read; EIO from a failing disk or stale NFS handle; the file is a named pipe or device whose writer closes early.

Common situations: Recorder still writing the file when transcription starts (partial file, then rewrite); audio captured on a network mount that drops mid-copy; path actually points at a FIFO.

Related errors


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