Billionmail/BillionMail · error
failed to create zip entry for %s: %v
Error message
failed to create zip entry for %s: %v
What it means
createZipFileInMemory builds a ZIP in memory using archive/zip. zipWriter.Create(name) returns a writer for a new entry; it fails when the archive writer is in a bad state (e.g. it was already closed) or the entry name is invalid for the ZIP format. This wrapper adds the offending file name to the error.
Source
Thrown at core/internal/controller/contact/contact_v1_export_contacts.go:217
}
// ExportFile Export file information
type ExportFile struct {
Name string // File name
Content string // File content
Contacts int // Contact count
}
// createZipFileInMemory creates a ZIP file in memory
func createZipFileInMemory(files []ExportFile) ([]byte, error) {
var buf bytes.Buffer
zipWriter := zip.NewWriter(&buf)
for _, file := range files {
// Create zip entry
zipEntry, err := zipWriter.Create(file.Name)
if err != nil {
return nil, fmt.Errorf("failed to create zip entry for %s: %v", file.Name, err)
}
// Write file content to zip
_, err = zipEntry.Write([]byte(file.Content))
if err != nil {
return nil, fmt.Errorf("failed to write content to zip for %s: %v", file.Name, err)
}
}
err := zipWriter.Close()
if err != nil {
return nil, fmt.Errorf("failed to close zip writer: %v", err)
}
return buf.Bytes(), nil
}
// exportContactsToCSV exports contacts to CSV formatView on GitHub (pinned to fc36c76c05)
Solutions
- Sanitize ExportFile.Name before building the ZIP (strip control characters, ensure valid UTF-8, reasonable length).
- Use errors.Is/As or %w wrapping to preserve the underlying cause for diagnostics.
- Ensure zipWriter.Close() is called exactly once, after all entries are written.
- For special characters in group names, set zipWriter's flags or normalize names to ASCII.
- If creating entries concurrently, serialize the calls — archive/zip.Writer is not safe for concurrent use.
Example fix
// before
zipEntry, err := zipWriter.Create(file.Name)
if err != nil {
return nil, fmt.Errorf("failed to create zip entry for %s: %v", file.Name, err)
}
// after
safeName := strings.Map(func(r rune) rune {
if r < 32 || r == 127 {
return -1
}
return r
}, file.Name)
zipEntry, err := zipWriter.Create(safeName)
if err != nil {
return nil, fmt.Errorf("failed to create zip entry for %q: %w", safeName, err)
} Defensive patterns
Strategy: validation
Validate before calling
func safeZipName(name string) string {
name = strings.TrimSpace(name)
if !utf8.ValidString(name) { name = strings.ToValidUTF8(name, "_") }
name = strings.Map(func(r rune) rune { if r < 32 || r == 127 { return -1 }; return r }, name)
if name == "" { name = "export.csv" }
return name
}
// apply to each ExportFile.Name before createZipFileInMemory Try / catch
out, err := createZipFileInMemory(files)
if err != nil {
var zerr *zip.Error
if errors.As(err, &zerr) {
return fmt.Errorf("zip export failed (%v); check file names", zerr)
}
return fmt.Errorf("export failed: %w", err)
} Prevention
- Sanitize entry names: no control chars, valid UTF-8, reasonable length
- Never reuse or close the zip.Writer between Create calls
- Serialize zip.Writer usage — it is not concurrency-safe
- Add a test that exports group names containing unicode/special characters
When it happens
Trigger: Calling createZipFileInMemory with a file whose Name cannot be used as a ZIP entry name (e.g. invalid UTF-8 in the name), or reusing/closing the zip.Writer across calls and then creating another entry; in practice with this code, a fresh zip.Writer only fails on malformed entry names.
Common situations: Group names with control or non-UTF-8 characters flowing into ExportFile.Name; code refactors that close the writer per entry; concurrent use of a single zip.Writer (not goroutine-safe).
Related errors
- failed to close zip writer: %v
- failed to write content to zip for %s: %v
- disk quota exceeded
- illegal file path:
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/c6537b09443088c5.
Report an issue: GitHub.