sipeed/picoclaw · error
failed to create form file: %w
Error message
failed to create form file: %w
What it means
Thrown when multipart.Writer.CreateFormFile("file", filepath.Base(audioFilePath)) fails while building the ElevenLabs upload. CreateFormFile writes a part header to a bytes.Buffer, so it only fails if the filename cannot be encoded into the Content-Disposition header (invalid characters) or the buffer write fails. Practically a configuration-of-input error, not a runtime one.
Source
Thrown at pkg/audio/asr/elevenlabs_transcriber.go:74
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})
return nil, fmt.Errorf("failed to copy file content: %w", err)
}
if err = writer.WriteField("model_id", t.modelID); err != nil {
return nil, fmt.Errorf("failed to write model_id field: %w", err)
}
if err = writer.Close(); err != nil {
logger.ErrorCF("voice", "Failed to close multipart writer", map[string]any{"error": err})
return nil, fmt.Errorf("failed to close multipart writer: %w", err)
}
url := t.apiBase + "/v1/speech-to-text"
req, err := http.NewRequestWithContext(ctx, "POST", url, &requestBody)View on GitHub (pinned to 49183d7e8d)
Solutions
- Sanitize the filename before transcription: strip control characters and keep an ASCII-safe base name.
- Reproduce by logging filepath.Base(audioFilePath) and checking for hidden \r/\n/non-UTF-8 bytes.
- If it happens without odd filenames, suspect memory pressure and profile the process.
Example fix
// before
part, err := writer.CreateFormFile("file", filepath.Base(audioFilePath))
// after (sanitize at the call site)
name := strings.Map(func(r rune) rune {
if r < 32 || r == 127 || r == '"' { return -1 }
return r
}, filepath.Base(audioFilePath))
part, err := writer.CreateFormFile("file", name) Defensive patterns
Strategy: validation
Validate before calling
func safePartName(path string) (string, error) {
name := filepath.Base(path)
if !utf8.ValidString(name) || strings.ContainsAny(name, "\"\r\n\\") {
return "", fmt.Errorf("unsafe audio filename %q", name)
}
return name, nil
} Type guard
func hasControlChars(s string) bool {
return strings.IndexFunc(s, func(r rune) bool { return r < 32 || r == 127 }) >= 0
} Try / catch
if _, err := el.Transcribe(ctx, path); err != nil && strings.Contains(err.Error(), "failed to create form file") {
return fmt.Errorf("audio filename rejected (%q): rename the file and retry", filepath.Base(path))
} Prevention
- Generate recording filenames yourself: timestamp + fixed extension, no user input.
- Reject non-UTF-8 or control-character filenames at intake.
When it happens
Trigger: The base filename contains characters the multipart header writer rejects, e.g. CR/LF, quotes, or non-UTF-8 bytes; otherwise only an out-of-memory condition on bytes.Buffer triggers it.
Common situations: Recordings named with embedded newlines or control characters (buggy recorder using a timestamp format with \n); filenames from another OS with invalid byte sequences.
Related errors
- failed to write model_id field: %w
- failed to close multipart writer: %w
- failed to create form file: %w
- failed to open audio file: %w
- failed to get file info: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/7289d05be78361cc.
Report an issue: GitHub.