sipeed/picoclaw · error

failed to get file info: %w

Error message

failed to get file info: %w

What it means

Thrown when audioFile.Stat() fails immediately after a successful os.Open in ElevenLabsTranscriber.Transcribe. On POSIX this is rare: the fd is already open, so Stat only fails if the fd is invalid (closed concurrently) or the kernel cannot fstat (NFS/virtual filesystems under load). The error wraps a *os.PathError with Op 'stat'.

Source

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

			Timeout: 120 * time.Second,
		},
	}
}

func (t *ElevenLabsTranscriber) Transcribe(ctx context.Context, audioFilePath string) (*TranscriptionResponse, error) {
	logger.InfoCF("voice", "Starting ElevenLabs transcription", map[string]any{"audio_file": audioFilePath})

	audioFile, err := os.Open(audioFilePath)
	if err != nil {
		logger.ErrorCF("voice", "Failed to open audio file", map[string]any{"path": audioFilePath, "error": err})
		return nil, fmt.Errorf("failed to open audio file: %w", err)
	}
	defer audioFile.Close()

	fileInfo, err := audioFile.Stat()
	if err != nil {
		logger.ErrorCF("voice", "Failed to get file info", map[string]any{"path": audioFilePath, "error": err})
		return nil, fmt.Errorf("failed to get file info: %w", err)
	}

	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})

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Audit for concurrent Close/deletion of the same audio file handle (go test -race).
  2. Copy the audio into a local temp file before transcribing when the source is a network mount.
  3. Retry once; if Stat keeps failing the storage layer is unhealthy.

Example fix

// before
audioFile, _ := os.Open(p)
fi, err := audioFile.Stat()

// after: single ownership, no concurrent close
audioFile, err := os.Open(p)
if err != nil { return nil, fmt.Errorf("open: %w", err) }
defer audioFile.Close()
fi, err := audioFile.Stat()
if err != nil { return nil, fmt.Errorf("stat after open (fs unhealthy?): %w", err) }
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check the same stat the transcriber performs, so failures surface on your terms:
fi, err := os.Stat(path)
if err != nil { return fmt.Errorf("stat failed before transcription: %w", err) }
if fi.Size() == 0 { return errors.New("empty audio file") }

Type guard

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

Try / catch

if _, err := el.Transcribe(ctx, path); err != nil {
    if isPathStatErr(err) {
        // storage layer or a race; copy file locally and retry once
        local, cerr := copyToLocal(path)
        if cerr == nil { _, err = el.Transcribe(ctx, local) }
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: The *os.File handle is closed by another goroutine between Open and Stat; the file lives on a failing network mount (NFS stale handle, EIO); filesystem returned an unexpected errno during fstat.

Common situations: Data race where a cleanup goroutine closes/deletes the temp file while transcription starts; flaky NFS/FUSE mount in containers; extremely rare on local ext4.

Related errors


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