sipeed/picoclaw · error

failed to read audio file: %w

Error message

failed to read audio file: %w

What it means

Thrown by AudioModelTranscriber.Transcribe when os.ReadFile cannot read the audio file before base64-encoding it into a chat message. The returned error wraps a *os.PathError naming the failing syscall (open/read/stat) and reason. The transcription never reaches the model provider; the wrapped message 'failed to read audio file' is added on top of the OS error.

Source

Thrown at pkg/audio/asr/audio_model_transcriber.go:59

	}

	return &AudioModelTranscriber{
		provider: provider,
		modelID:  modelID,
		prompt:   defaultTranscriptionPrompt,
	}
}

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

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

	format, err := utils.AudioFormat(audioFilePath)
	if err != nil {
		logger.ErrorCF("voice", "Failed to detect audio format", map[string]any{"path": audioFilePath, "error": err})
		return nil, err
	}

	resp, err := t.provider.Chat(ctx, []providers.Message{
		{
			Role:    "user",
			Content: t.prompt,
			Media: []string{
				fmt.Sprintf("data:audio/%s;base64,%s", format, base64.StdEncoding.EncodeToString(audioBytes)),
			},
		},
	}, nil, t.modelID, map[string]any{
		"temperature": 0,

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Check that audioFilePath exists and is a regular, readable file before calling Transcribe (os.Stat + mode check).
  2. Print the wrapped OS error (errors.Unwrap / err.Error()) to see ENOENT vs EACCES vs EISDIR and fix the path or permissions accordingly.
  3. If the file is produced by a recorder, make it write to a stable absolute path under a directory the process owns.
  4. If the path comes from user input, resolve and validate it against an allow-listed directory.

Example fix

// before
resp, err := transcriber.Transcribe(ctx, "audio/input.mp3")
if err != nil { return err }

// after
if fi, err := os.Stat("audio/input.mp3"); err != nil || fi.IsDir() {
    return fmt.Errorf("audio file not ready: %w", err)
}
resp, err := transcriber.Transcribe(ctx, "audio/input.mp3")
if err != nil {
    if errors.Is(err, fs.ErrNotExist) {
        return fmt.Errorf("audio file disappeared before transcription: %w", err)
    }
    return err
}
Defensive patterns

Strategy: validation

Validate before calling

// Run before Transcribe:
func audioReadable(path string) error {
    fi, err := os.Stat(path)
    if err != nil { return fmt.Errorf("audio file inaccessible: %w", err) }
    if fi.IsDir() { return fmt.Errorf("audio path is a directory: %s", path) }
    if fi.Size() == 0 { return fmt.Errorf("audio file is empty: %s", path) }
    if fi.Mode().Perm()&0o400 == 0 { return fmt.Errorf("audio file not readable: %s", path) }
    return nil
}

Type guard

func isFileMissing(err error) bool {
    return errors.Is(err, fs.ErrNotExist)
}
func isPermissionErr(err error) bool {
    return errors.Is(err, fs.ErrPermission)
}

Try / catch

if err := transcriber.Transcribe(ctx, path); err != nil {
    switch {
    case errors.Is(err, fs.ErrNotExist):
        // producer bug: file gone before transcription
    case errors.Is(err, fs.ErrPermission):
        // fix ownership/permissions, do not retry
    default:
        // real read error (I/O): log and surface
    }
}

Prevention

When it happens

Trigger: os.ReadFile(audioFilePath) fails because: the path does not exist (ENOENT), permission denied (EACCES, common when the agent runs as another user), the path is a directory (EISDIR), or the file was deleted between recording and transcription. Occurs before provider.Chat is called.

Common situations: Agent writes audio to a temp dir that another process cleaned up; relative path evaluated from a different working directory; container volume mount hides the file; file owned by root while the service runs unprivileged.

Related errors


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