{"record":{"id":"704b244835192609","repo":"sipeed/picoclaw","slug":"failed-to-stat-audio-file-s-w","errorCode":null,"errorMessage":"failed to stat audio file %s: %w","messagePattern":"failed to stat audio file (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/audio/asr/whisper_transcriber.go","lineNumber":148,"sourceCode":"\treturn t.doRequest(ctx, &requestBody, writer.FormDataContentType(), int64(len(data)))\n}\n\nfunc (t *WhisperTranscriber) Transcribe(ctx context.Context, audioFilePath string) (*TranscriptionResponse, error) {\n\tlogger.InfoCF(\"voice\", \"Starting whisper transcription\", map[string]any{\n\t\t\"audio_file\": audioFilePath,\n\t\t\"model\":      t.modelID,\n\t\t\"provider\":   t.providerName,\n\t})\n\n\taudioFile, err := os.Open(audioFilePath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open audio file %s: %w\", audioFilePath, err)\n\t}\n\tdefer audioFile.Close()\n\n\tfileInfo, err := audioFile.Stat()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to stat audio file %s: %w\", audioFilePath, err)\n\t}\n\n\tvar requestBody bytes.Buffer\n\twriter := multipart.NewWriter(&requestBody)\n\n\tpart, err := writer.CreateFormFile(\"file\", filepath.Base(audioFilePath))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create form file: %w\", err)\n\t}\n\n\tif _, copyErr := io.Copy(part, audioFile); copyErr != nil {\n\t\treturn nil, fmt.Errorf(\"failed to copy audio data: %w\", copyErr)\n\t}\n\n\tif err = writer.WriteField(\"model\", t.modelID); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to write model field: %w\", err)\n\t}\n","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/audio/asr/whisper_transcriber.go#L130-L166","documentation":"Thrown when audioFile.Stat() fails right after a successful os.Open in WhisperTranscriber.Transcribe. Same rare class as the ElevenLabs stat error (303): the fd is open, so fstat fails only on a concurrently-closed handle, a broken network filesystem (stale NFS handle, EIO), or a kernel-level surprise. The size from this Stat feeds later logging and doRequest.","triggerScenarios":"Another goroutine closes or replaces the file between Open and Stat; NFS/FUSE mount returns an error for fstat; fd table corruption after an fd leak elsewhere.","commonSituations":"Race between temp-file cleanup and transcription start; containerized workloads on network volumes.","solutions":["Serialize cleanup and transcription (delete only after Transcribe returns).","Copy network-mounted audio to local disk first.","Run with -race to find concurrent Close of the same path."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// The library Stats right after Open; do the same check first to own the error message:\nfi, err := os.Stat(path)\nif err != nil { return fmt.Errorf(\"audio unstable before transcription: %w\", err) }\nif fi.Size() == 0 { return errors.New(\"empty audio file\") }","typeGuard":"func isStatFailure(err error) bool {\n    var pe *os.PathError\n    return errors.As(err, &pe) && pe.Op == \"stat\"\n}","tryCatchPattern":"if _, err := whisper.Transcribe(ctx, path); err != nil {\n    if isStatFailure(err) {\n        // fd/fs-level inconsistency: serialize lifecycle, copy locally, retry once\n        return retryOnceAfterCopy(ctx, path)\n    }\n    return err\n}","preventionTips":["One owner per audio file: no concurrent Close/Unlink during transcription.","Copy audio off network mounts before transcribing.","Test the voice pipeline with -race in CI."],"tags":["filesystem","concurrency","whisper","audio","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}