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 format

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Sanitize ExportFile.Name before building the ZIP (strip control characters, ensure valid UTF-8, reasonable length).
  2. Use errors.Is/As or %w wrapping to preserve the underlying cause for diagnostics.
  3. Ensure zipWriter.Close() is called exactly once, after all entries are written.
  4. For special characters in group names, set zipWriter's flags or normalize names to ASCII.
  5. 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

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


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/c6537b09443088c5. Report an issue: GitHub.