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

  1. Inspect the raw attribs value for the failing contact and validate it as JSON (e.g. SELECT attribs::jsonb ...)
  2. Ensure all enrichment/import writers store only string values (convert numbers/bools with fmt.Sprintf or strconv)
  3. Flatten nested JSON before writing, or change the Scan target to map[string]interface{} and coerce types manually
  4. 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

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


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