Billionmail/BillionMail · error
parse contact attribs: %w
Error message
parse contact attribs: %w
What it means
loadContactAttribs calls val.Scan(&result) to deserialize the attribs database value into map[string]string. This error wraps whatever Scan returns when the stored value cannot be decoded into that map — typically malformed JSON, or a JSON object whose values are not strings (numbers, booleans, nested objects). It indicates the attribs payload is present but not shaped as map[string]string.
Source
Thrown at core/internal/service/video_gen/orchestrator.go:337
updateJobStatus(ctx, jobID, JobFailed, phase, err.Error())
}
// loadContactAttribs fetches attribs for a contact from DB.
func loadContactAttribs(ctx context.Context, contactID, groupID int) (map[string]string, error) {
val, err := g.DB().Model("bm_contacts").Ctx(ctx).
Where("id", contactID).
Where("group_id", groupID).
Value("attribs")
if err != nil {
return nil, fmt.Errorf("load contact attribs: %w", err)
}
if val.IsNil() || val.IsEmpty() {
return nil, fmt.Errorf("contact %d has no attribs", contactID)
}
result := make(map[string]string)
if err := val.Scan(&result); err != nil {
return nil, fmt.Errorf("parse contact attribs: %w", err)
}
return result, nil
}
// updateContactVideoAttribs merges video URLs into Contact.Attribs.
func updateContactVideoAttribs(ctx context.Context, contactID, groupID int, videoURL, thumbURL, landingURL string) error {
// Load existing attribs
attribs, err := loadContactAttribs(ctx, contactID, groupID)
if err != nil {
return err
}
attribs["video_url"] = videoURL
attribs["thumbnail_url"] = thumbURL
attribs["landing_page_url"] = landingURL
_, err = g.DB().Model("bm_contacts").Ctx(ctx).
Where("id", contactID).View on GitHub (pinned to fc36c76c05)
Solutions
- Inspect the raw attribs value for the failing contact and validate it as JSON (e.g. SELECT attribs::jsonb ...)
- Ensure all enrichment/import writers store only string values (convert numbers/bools with fmt.Sprintf or strconv)
- Flatten nested JSON before writing, or change the Scan target to map[string]interface{} and coerce types manually
- Add a data migration that normalizes or clears malformed attribs rows
Example fix
// before attribs["email_count"] = 5 // stored as JSON number, breaks map[string]string scan // after attribs["email_count"] = "5" // store all values as strings
Defensive patterns
Strategy: validation
Validate before calling
var probe map[string]interface{}
if err := json.Unmarshal([]byte(rawAttribs), &probe); err != nil {
return fmt.Errorf("contact %d has malformed attribs JSON: %w", contactID, err)
}
for k, v := range probe {
if _, ok := v.(string); !ok {
return fmt.Errorf("attribs key %q is not a string (got %T)", k, v)
}
} Type guard
func isStringMap(m map[string]interface{}) bool {
for _, v := range m {
if _, ok := v.(string); !ok {
return false
}
}
return true
} Try / catch
result, err := loadContactAttribs(ctx, contactID, groupID)
if err != nil {
if strings.HasPrefix(err.Error(), "parse contact attribs:") {
// corrupt JSON: quarantine the record for manual/automated repair
quarantineAttribs(contactID, groupID, err)
return err
}
return err
} Prevention
- Store attribs as JSONB and marshal through a typed struct so writers can't emit non-string values
- Always json.Marshal maps before persisting; never interpolate raw strings
- Add a periodic job validating attribs parses as JSON across bm_contacts
- Centralize attribs writes in one service function instead of scattered SQL updates
When it happens
Trigger: attribs column contains invalid JSON (truncated write, plain text instead of JSON); attribs is a JSON object with non-string values, e.g. {"email_count": 5} or nested objects like {"signals": {"seo": true}} which cannot Scan into map[string]string.
Common situations: Import tools writing raw CSV cell text into attribs; enrichment code storing numeric or nested values instead of stringified ones; manual SQL edits corrupting the JSON; older schema where attribs was plain text and rows were never migrated.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
- failed to parse alert settings: %v
- error reading CSV file: %v
- failed to read CSV headers: %v
- failed to convert overview data: %v
- failed to convert overview data: %v
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/3efc9afafc70c155.
Report an issue: GitHub.