{"record":{"id":"4993c9a0f4995909","repo":"Billionmail/BillionMail","slug":"contact-d-has-no-attribs","errorCode":null,"errorMessage":"contact %d has no attribs","messagePattern":"contact (.+?) has no attribs","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/orchestrator.go","lineNumber":332,"sourceCode":"}\n\n// markFailed sets job status to failed with error message and logs it.\nfunc markFailed(ctx context.Context, jobID int, phase string, err error) {\n\tg.Log().Errorf(ctx, \"video job %d failed at %s: %v\", jobID, phase, err)\n\tupdateJobStatus(ctx, jobID, JobFailed, phase, err.Error())\n}\n\n// loadContactAttribs fetches attribs for a contact from DB.\nfunc loadContactAttribs(ctx context.Context, contactID, groupID int) (map[string]string, error) {\n\tval, err := g.DB().Model(\"bm_contacts\").Ctx(ctx).\n\t\tWhere(\"id\", contactID).\n\t\tWhere(\"group_id\", groupID).\n\t\tValue(\"attribs\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"load contact attribs: %w\", err)\n\t}\n\tif val.IsNil() || val.IsEmpty() {\n\t\treturn nil, fmt.Errorf(\"contact %d has no attribs\", contactID)\n\t}\n\n\tresult := make(map[string]string)\n\tif err := val.Scan(&result); err != nil {\n\t\treturn nil, fmt.Errorf(\"parse contact attribs: %w\", err)\n\t}\n\treturn result, nil\n}\n\n// updateContactVideoAttribs merges video URLs into Contact.Attribs.\nfunc updateContactVideoAttribs(ctx context.Context, contactID, groupID int, videoURL, thumbURL, landingURL string) error {\n\t// Load existing attribs\n\tattribs, err := loadContactAttribs(ctx, contactID, groupID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tattribs[\"video_url\"] = videoURL","sourceCodeStart":314,"sourceCodeEnd":350,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/orchestrator.go#L314-L350","documentation":"loadContactAttribs raises this when the query executed successfully but the attribs value is nil or empty — meaning no matching bm_contacts row (wrong id or group_id) or a row whose attribs column is NULL/empty. The pipeline cannot personalize the video without attribs, so the job fails at the load_contact phase. This is a data-existence error, distinct from a query error (466) or a JSON parse error (468).","triggerScenarios":"Query returns no row for (contactID, groupID); the row exists but attribs is NULL; attribs is an empty string or empty JSON object that GoFrame's Value reports as empty.","commonSituations":"Contact deleted between enqueue and processing; job enqueued with a mismatched group_id (contacts are scoped per group); contact imported without any attribute mapping so attribs stayed NULL; dev/staging DB pointed at a dataset lacking the contact.","solutions":["SELECT id, group_id, attribs FROM bm_contacts WHERE id=<contactID> to see whether the row exists and what attribs contains","Verify the group_id used at enqueue matches the contact's actual group_id","Ensure the import pipeline always writes a valid JSON object into attribs, never NULL","If the contact was deleted, purge or skip its pending bm_video_jobs rows instead of letting them fail each poll cycle"],"exampleFix":"// before\nresult, err := EnqueueVideoJob(ctx, contactID, email, groupID)\n// after\nexists, _ := g.DB().Model(\"bm_contacts\").Ctx(ctx).Where(\"id\", contactID).Where(\"group_id\", groupID).Count()\nif exists == 0 {\n    return fmt.Errorf(\"contact %d not found in group %d\", contactID, groupID)\n}\nresult, err := EnqueueVideoJob(ctx, contactID, email, groupID)","handlingStrategy":"validation","validationCode":"count, err := g.DB().Model(\"bm_contacts\").Ctx(ctx).\n    Where(\"id\", contactID).Where(\"group_id\", groupID).\n    Where(\"attribs IS NOT NULL AND attribs <> '' AND attribs <> '{}'\").Count()\nif err != nil || count == 0 {\n    return fmt.Errorf(\"contact %d not eligible (missing/empty attribs in group %d)\", contactID, groupID)\n}","typeGuard":null,"tryCatchPattern":"_, err := loadContactAttribs(ctx, contactID, groupID)\nif err != nil && strings.Contains(err.Error(), \"has no attribs\") {\n    // permanent data problem: fail the job without retries\n    cancelJob(jobID, \"contact has no attribs\")\n}","preventionTips":["Never insert bm_video_jobs for a contact until its attribs are confirmed populated","Use a DB default or NOT NULL JSONB '{}' plus an application check for required keys","Cascade-delete pending video jobs when a contact is removed","Keep group_id coupling consistent: derive it from the contact row at enqueue time, not from caller input"],"tags":["database","missing-record","contacts","data-quality"],"backgroundTag":"record-not-found","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"}