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", filename) fails in WhisperTranscriber's bytes-based path (transcribing in-memory data). Identical semantics to the ElevenLabs case: the filename cannot be encoded into the Content-Disposition header (control characters, CR/LF, non-UTF-8 bytes) or the destination bytes.Buffer cannot allocate. The data itself is already in memory, so file I/O is not involved.
Source
Thrown at pkg/audio/asr/whisper_transcriber.go:107
func (t *WhisperTranscriber) TranscribeData(
ctx context.Context,
data []byte,
filename string,
) (*TranscriptionResponse, error) {
logger.InfoCF("voice", "Starting whisper transcription from memory", map[string]any{
"bytes": len(data),
"filename": filename,
"model": t.modelID,
"provider": t.providerName,
})
var requestBody bytes.Buffer
writer := multipart.NewWriter(&requestBody)
part, err := writer.CreateFormFile("file", filename)
if err != nil {
logger.ErrorCF("voice", "Failed to create whisper form file", map[string]any{"error": err})
return nil, fmt.Errorf("failed to create form file: %w", err)
}
if _, copyErr := io.Copy(part, bytes.NewReader(data)); copyErr != nil {
logger.ErrorCF("voice", "Failed to copy whisper file content", map[string]any{"error": copyErr})
return nil, fmt.Errorf("failed to copy file content: %w", copyErr)
}
if err = writer.WriteField("model", t.modelID); err != nil {
logger.ErrorCF("voice", "Failed to write whisper model field", map[string]any{"error": err})
return nil, fmt.Errorf("failed to write model field: %w", err)
}
if err = writer.WriteField("response_format", "json"); err != nil {
logger.ErrorCF("voice", "Failed to write whisper response_format field", map[string]any{"error": err})
return nil, fmt.Errorf("failed to write response_format field: %w", err)
}
if err = writer.Close(); err != nil {View on GitHub (pinned to 49183d7e8d)
Solutions
- Sanitize the filename: keep [A-Za-z0-9._-] plus a known extension.
- Log the filename with %q to reveal invisible characters.
- If filenames are clean, treat as memory pressure (see buffer errors).
Example fix
// before
part, err := writer.CreateFormFile("file", filename)
// after
var b strings.Builder
for _, r := range filename {
if r >= 32 && r < 127 && r != '"' { b.WriteRune(r) }
}
safe := b.String()
if safe == "" { safe = "audio.wav" }
part, err := writer.CreateFormFile("file", safe) Defensive patterns
Strategy: validation
Validate before calling
func validUploadName(name string) error {
if !utf8.ValidString(name) || strings.ContainsAny(name, "\"\r\n\\;") {
return fmt.Errorf("rejecting unsafe upload filename %q", name)
}
return nil
} Type guard
func hasUnsafeMimeChars(s string) bool {
return strings.IndexFunc(s, func(r rune) bool { return r < 32 || r == 127 || r == '"' || r == '\\' }) >= 0
} Try / catch
if err := validUploadName(filename); err != nil { return err }
if _, err := whisper.TranscribeWithData(ctx, data, filename); err != nil {
if strings.Contains(err.Error(), "failed to create form file") {
return fmt.Errorf("filename %q rejected; rename and retry", filename)
}
return err
} Prevention
- Derive filenames from trusted constants plus a timestamp; never splice user text into them.
- Validate filenames at the API boundary, not inside the HTTP client.
When it happens
Trigger: Caller-supplied filename (used for both the form field and format detection elsewhere) contains \r, \n, quotes, or invalid UTF-8; memory exhaustion while buffering large audio.
Common situations: Filename built by string concatenation that accidentally embeds user text or a newline; blobs transcoded with machine-generated names containing odd bytes.
Related errors
- failed to create form file: %w
- failed to copy file content: %w
- failed to write model_id field: %w
- failed to close multipart writer: %w
- failed to write model field: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/85889fa42e49f5cf.
Report an issue: GitHub.