{"record":{"id":"80a7fce0ff33b0e1","repo":"sipeed/picoclaw","slug":"failed-to-open-audio-file-s-w","errorCode":null,"errorMessage":"failed to open audio file %s: %w","messagePattern":"failed to open audio file (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/audio/asr/whisper_transcriber.go","lineNumber":142,"sourceCode":"\n\tif err = writer.Close(); err != nil {\n\t\tlogger.ErrorCF(\"voice\", \"Failed to close whisper multipart writer\", map[string]any{\"error\": err})\n\t\treturn nil, fmt.Errorf(\"failed to close multipart writer: %w\", err)\n\t}\n\n\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)","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/audio/asr/whisper_transcriber.go#L124-L160","documentation":"Thrown when os.Open(audioFilePath) fails at the start of WhisperTranscriber.Transcribe — the file-based counterpart of the bytes-based path. Wraps a *os.PathError with Op 'open'; nothing else (model config, network, API key) is consulted yet, so the whisper endpoint/provider is irrelevant to this error.","triggerScenarios":"ENOENT (file not yet written or already deleted), EACCES (read permission), EISDIR (directory passed), ELOOP (symlink loop from broken temp-dir cleanup).","commonSituations":"Voice pipeline deletes the temp file on context cancellation while the transcription goroutine is starting; recordings directory rotated between capture and transcription; path with a trailing newline from config.","solutions":["os.Stat the file immediately before calling Transcribe and treat missing files as a pipeline-ordering bug.","Fix permissions or path construction; prefer absolute paths.","Ensure cleanup callbacks do not race the transcription (delete after Transcribe returns)."],"exampleFix":"// before\nresp, err := whisper.Transcribe(ctx, path)\n\n// after\nif _, statErr := os.Stat(path); statErr != nil {\n    return nil, fmt.Errorf(\"audio not available for whisper: %w\", statErr)\n}\nresp, err := whisper.Transcribe(ctx, path)","handlingStrategy":"validation","validationCode":"func ensureAudioFile(path string) (*os.FileInfo, error) {\n    fi, err := os.Stat(path)\n    if err != nil { return nil, fmt.Errorf(\"audio file not ready: %w\", err) }\n    if fi.IsDir() { return nil, fmt.Errorf(\"audio path is a directory: %s\", path) }\n    if fi.Size() == 0 { return nil, fmt.Errorf(\"audio file empty: %s\", path) }\n    return &fi, nil\n}","typeGuard":"func isMissingFile(err error) bool { return errors.Is(err, fs.ErrNotExist) }","tryCatchPattern":"if _, err := whisper.Transcribe(ctx, path); err != nil {\n    if isMissingFile(err) {\n        // ordering bug in the voice pipeline: file deleted/renamed before transcription\n        return nil, fmt.Errorf(\"audio lifecycle bug, %s missing: %w\", path, err)\n    }\n    return nil, err\n}","preventionTips":["Schedule deletion after transcription completes, not on cancel.","Pre-stat and size-check every file before Transcribe.","Use absolute paths under a directory the service owns."],"tags":["filesystem","whisper","audio","transcription","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}