{"record":{"id":"220d2a772caa61bc","repo":"Billionmail/BillionMail","slug":"failed-to-close-zip-writer-v","errorCode":null,"errorMessage":"failed to close zip writer: %v","messagePattern":"failed to close zip writer: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/controller/contact/contact_v1_export_contacts.go","lineNumber":229,"sourceCode":"\tzipWriter := zip.NewWriter(&buf)\n\n\tfor _, file := range files {\n\t\t// Create zip entry\n\t\tzipEntry, err := zipWriter.Create(file.Name)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create zip entry for %s: %v\", file.Name, err)\n\t\t}\n\n\t\t// Write file content to zip\n\t\t_, err = zipEntry.Write([]byte(file.Content))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to write content to zip for %s: %v\", file.Name, err)\n\t\t}\n\t}\n\n\terr := zipWriter.Close()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to close zip writer: %v\", err)\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\n// exportContactsToCSV exports contacts to CSV format\nfunc exportContactsToCSV(contacts []*entity.Contact) (string, error) {\n\tvar buf bytes.Buffer\n\twriter := csv.NewWriter(&buf)\n\n\t// Write CSV headers\n\theaders := []string{\n\t\t\"email\",       // Email address\n\t\t\"attributes\",  // Attributes\n\t\t\"active\",      // Active status\n\t\t\"create_time\", // Create time\n\t}\n\tif err := writer.Write(headers); err != nil {","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/controller/contact/contact_v1_export_contacts.go#L211-L247","documentation":"archive/zip's Writer.Close finalizes the archive: it writes the central directory and any buffered data. Close fails if any previously written entry returned an error on flush/close, or if the underlying writer (bytes.Buffer here) errors — which for an in-memory buffer is essentially impossible, making this a defensive guard that matters only if the sink changes.","triggerScenarios":"Underlying writer I/O failure during central-directory write (real for file/network sinks, not for bytes.Buffer); calling Close twice or after the writer was handed to another goroutine; a prior entry writer left in a broken state.","commonSituations":"Disk full when writing export ZIPs to disk; interrupted container filesystem; refactors that defer close twice; returning the buffer before Close completes, producing a truncated/unopenable ZIP that users report as 'corrupt export'.","solutions":["With the current in-memory buffer this should never fire; if it does after changing the sink, verify disk space and file permissions.","Call zipWriter.Close exactly once, before returning buf.Bytes(); never rely on defer alone if the return value depends on Close.","Wrap with %w to let callers distinguish close errors from entry errors.","If streaming to HTTP, flush the response writer before Close and handle client-abort errors.","Validate the produced ZIP (e.g. zip.NewReader) in tests to catch silent truncation regressions."],"exampleFix":"// before\nerr := zipWriter.Close()\nif err != nil {\n    return nil, fmt.Errorf(\"failed to close zip writer: %v\", err)\n}\n// after\nif err := zipWriter.Close(); err != nil {\n    return nil, fmt.Errorf(\"failed to close zip writer: %w\", err)\n}\nif buf.Len() == 0 {\n    return nil, fmt.Errorf(\"zip writer closed with empty output\")\n}","handlingStrategy":"try-catch","validationCode":"// pre-flight: ensure entries exist and names are valid so Close-time state is clean\nif len(files) == 0 { return errors.New(\"nothing to export\") }\nfor _, f := range files {\n    if utf8.ValidString(f.Name) == false { return fmt.Errorf(\"invalid name %q\", f.Name) }\n}","typeGuard":null,"tryCatchPattern":"zipBytes, err := createZipFileInMemory(files)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to close zip writer\") {\n        return fmt.Errorf(\"export archive could not be finalized; underlying storage error: %w\", err)\n    }\n    return err\n}\n// serve zipBytes...","preventionTips":["Call zipWriter.Close exactly once and check its error before returning bytes","Never defer both Close and a manual Close on the same writer","After changing the sink from bytes.Buffer, handle disk-full (ENOSPC) on Close","In tests, reopen the produced bytes with zip.NewReader to verify archive integrity"],"tags":["zip","archive","export","resource-cleanup"],"backgroundTag":"zip-writer-close-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}