Billionmail/BillionMail · error

error reading CSV file: %v

Error message

error reading CSV file: %v

What it means

parseCSVFile reads all records with encoding/csv's reader.ReadAll() after enabling TrimLeadingSpace, LazyQuotes, and ReuseRecord. If the CSV tokenizer hits a structural problem — e.g. a bare quote mid-field, a quote not followed by a delimiter/newline, or an extraneous or missing quote — ReadAll returns a csv.ParseError and it is wrapped with this message. The wrapper discards the underlying error's line context in the string, though the %v verb keeps the wrapped text.

Source

Thrown at core/internal/controller/contact/contact.go:101

	//fmt.Printf("parseTXTFile - Total valid contacts: %d\n", len(contacts))
	return contacts, nil
}

// parseCSVFile parses CSV file content
func parseCSVFile(fileContent []byte) ([]*entity.Contact, error) {
	// handle possible BOM
	fileContent = bytes.TrimPrefix(fileContent, []byte("\xef\xbb\xbf"))

	reader := csv.NewReader(bytes.NewReader(fileContent))
	reader.FieldsPerRecord = -1 // Allow different number of fields per line
	reader.TrimLeadingSpace = true
	reader.LazyQuotes = true  // Allow non-strict quotes
	reader.ReuseRecord = true // Reuse record to improve performance

	records, err := reader.ReadAll()
	if err != nil {
		return nil, fmt.Errorf("error reading CSV file: %v", err)
	}

	var contacts []*entity.Contact
	var hasHeader bool

	// Check if header exists
	if len(records) > 0 {
		firstRow := records[0]
		if len(firstRow) >= 1 && strings.ToLower(firstRow[0]) == "email" {
			hasHeader = true
		}
	}

	// Process records
	startIndex := 0
	if hasHeader {
		startIndex = 1
	}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Open the CSV and fix the malformed quoting on the reported line; the wrapped error text (kept via %v) includes the line/column.
  2. Re-export the file from the source tool with proper quoting (quote all fields).
  3. Consider wrapping with %w instead of %v so callers can errors.As(csv.ParseError) and report an exact row to the user.
  4. If files use a different delimiter, set reader.Comma before ReadAll; non-comma delimiters can cause quote-parse errors.
  5. If the file may be huge, switch from ReadAll to reader.Read() streaming to avoid memory pressure (does not fix parse errors but avoids OOM-path failures).

Example fix

// before
records, err := reader.ReadAll()
if err != nil {
    return nil, fmt.Errorf("error reading CSV file: %v", err)
}
// after
records, err := reader.ReadAll()
if err != nil {
    var perr *csv.ParseError
    if errors.As(err, &perr) {
        return nil, fmt.Errorf("error reading CSV file at line %d, column %d: %w", perr.Line, perr.Column, err)
    }
    return nil, fmt.Errorf("error reading CSV file: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// cheap pre-checks before parsing:
if len(fileContent) == 0 { return errors.New("empty CSV file") }
sniff := string(fileContent[:min(1024, len(fileContent))])
// reject obvious binary content uploaded as CSV
if strings.ContainsRune(sniff, 0) { return errors.New("file is not valid CSV text") }

Try / catch

records, err := parseCSVFile(data)
if err != nil {
    var perr *csv.ParseError
    if errors.As(err, &perr) {
        return fmt.Errorf("invalid CSV at line %d, column %d — fix quoting and re-upload", perr.Line, perr.Column)
    }
    return fmt.Errorf("CSV import failed: %w", err)
}

Prevention

When it happens

Trigger: Uploading a contacts CSV where a field contains an unescaped quote (LazyQuotes only forgives some cases), a quote character is not immediately followed by a comma/newline, or the file mixes line endings with stray quotes. Any ReadAll parse error on the uploaded bytes.

Common situations: CSVs exported from Excel with embedded quotes in names; user-edited CSVs with mismatched quotes; TSV or semicolon-separated files mislabeled as CSV; files pasted from email clients introducing smart quotes (U+201C/U+201D) that csv.Reader treats as invalid.

Related errors


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