Billionmail/BillionMail · error
failed to read CSV headers: %v
Error message
failed to read CSV headers: %v
What it means
parseCSVContent wraps the error returned by encoding/csv Reader.Read() when it fails to read the first (header) record of an uploaded contacts CSV. LazyQuotes and TrimLeadingSpace are enabled, so this fires only on structural CSV problems, not sloppy quoting. The underlying cause is preserved via %v in the wrapped message.
Source
Thrown at core/internal/controller/contact/contact_v1_import_contacts.go:108
}
}
g.Log().Debug(ctx, "Final parsed attributes: %v", stringAttribs)
return stringAttribs, nil
}
// parseCSVContent Parse CSV content
func parseCSVContent(ctx context.Context, content string) ([]*entity.Contact, error) {
var contacts []*entity.Contact
reader := csv.NewReader(bytes.NewReader([]byte(content)))
reader.FieldsPerRecord = -1
reader.LazyQuotes = true
reader.TrimLeadingSpace = true
// Read headers
headers, err := reader.Read()
if err != nil {
return nil, fmt.Errorf("failed to read CSV headers: %v", err)
}
// Find the index of the required columns
columnIndexes := make(map[string]int)
for i, header := range headers {
header = strings.ToLower(strings.TrimSpace(header))
columnIndexes[header] = i
}
// Check the required columns
if _, ok := columnIndexes["email"]; !ok {
return nil, fmt.Errorf("required column 'email' not found")
}
// Process data rows
for {
record, err := reader.Read()
if err == io.EOF {View on GitHub (pinned to fc36c76c05)
Solutions
- Open the uploaded CSV in a spreadsheet/text editor and fix the header row: equal number of comma-separated columns on every row.
- Ensure the file is valid UTF-8 without a stray BOM and uses consistent CRLF/LF line endings.
- If rows legitimately have different lengths, set reader.FieldsPerRecord = -1 before Read() to disable the field-count check.
- Verify the uploaded file is a real CSV, not an xlsx or HTML error page saved with .csv extension.
Example fix
// before reader.LazyQuotes = true reader.TrimLeadingSpace = true headers, err := reader.Read() // after reader.LazyQuotes = true reader.TrimLeadingSpace = true reader.FieldsPerRecord = -1 // tolerate ragged rows headers, err := reader.Read()
Defensive patterns
Strategy: validation
Validate before calling
data, err := os.ReadFile(path)
if err != nil { return err }
if !utf8.Valid(data) { return errors.New("CSV is not valid UTF-8") }
r := csv.NewReader(bytes.NewReader(data))
r.FieldsPerRecord = -1
if _, err := r.Read(); err != nil {
return fmt.Errorf("invalid CSV header row: %w", err)
} Try / catch
headers, err := parseCSVContent(ctx, file)
if err != nil {
var csvErr *csv.ParseError
if errors.As(err, &csvErr) {
return fmt.Errorf("CSV malformed at line %d: %w", csvErr.Line, err)
}
return err
} Prevention
- Re-exports CSVs from the same tool and verify headers/rows in a spreadsheet before upload
- Save files as UTF-8 without BOM
- Validate field-count consistency before importing large files
- Strip non-CSV artifacts (xlsx, HTML) at upload time
When it happens
Trigger: reader.Read() on the header line returns a non-EOF error, e.g. a field count mismatch against the first record when useFieldsPerRecord defaults (headers row has 3 columns, later rows have 5), or a bare-quote/rune-reading error that LazyQuotes did not suppress.
Common situations: Users export contacts from Excel/Google Sheets with ragged rows; CSVs with embedded quotes or non-UTF8 bytes; empty or truncated uploads that produce a malformed header; BOM or CRLF quirks shifting field parsing.
Related errors
- error reading CSV file: %v
- unsupported file type: %s
- required column 'email' not found
- failed to parse alert settings: %v
- Failed to parse DKIM record: %v
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/572bff87d2f0727e.
Report an issue: GitHub.