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
- Audit for concurrent Close/deletion of the same audio file handle (go test -race).
- Copy the audio into a local temp file before transcribing when the source is a network mount.
- 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
- Never share the audio file handle across goroutines.
- Copy network-mounted audio to local disk before transcription.
- Enable the race detector in CI for the audio pipeline.
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
- failed to open audio file: %w
- failed to copy file content: %w
- failed to stat audio file %s: %w
- failed to read audio file: %w
- failed to create form file: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/80934dc82eb37455.
Report an issue: GitHub.