{"record":{"id":"78ee8802479a7877","repo":"Billionmail/BillionMail","slug":"load-contact-attribs-w","errorCode":null,"errorMessage":"load contact attribs: %w","messagePattern":"load contact attribs: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/orchestrator.go","lineNumber":329,"sourceCode":"\tos.RemoveAll(tmpDir)\n\n\treturn nil\n}\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","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/orchestrator.go#L311-L347","documentation":"loadContactAttribs wraps the underlying error from the GoFrame DB query (g.DB().Model(\"bm_contacts\")...Value(\"attribs\")) that fetches the attribs column for a given contact id and group_id. This is a database-layer failure, not a data problem: the wrapped error carries the real cause (connectivity, bad SQL, schema drift). Callers (RunPipeline, updateContactVideoAttribs) surface it and the job is marked failed at the load_contact phase.","triggerScenarios":"The SELECT attribs FROM bm_contacts WHERE id=? AND group_id=? query returns an error: DB unreachable, connection pool exhausted, bm_contacts table missing/renamed, permission denied, or driver/timeout errors.","commonSituations":"PostgreSQL container down or restarting during a deploy; DATABASE_URL/DB config mispointed in staging; migration hasn't created bm_contacts in a fresh environment; transient network blips between app and DB; too many open connections under load from parallel video jobs.","solutions":["Read the wrapped error in the job's error column / logs to identify the root DB cause","Verify the database is reachable and healthy (pg_isready, connection config in manifest/config)","Confirm the bm_contacts table exists with an attribs column in the target database","Check DB user permissions for SELECT on bm_contacts","Retry after transient outages; consider verifying DB connectivity before launching the pipeline"],"exampleFix":"// before\nval, err := g.DB().Model(\"bm_contacts\").Ctx(ctx).Where(\"id\", contactID).Where(\"group_id\", groupID).Value(\"attribs\")\n// after\nval, err := g.DB().Model(\"bm_contacts\").Ctx(ctx).Timeout(10*time.Second).Where(\"id\", contactID).Where(\"group_id\", groupID).Value(\"attribs\")\nif err != nil {\n    g.Log().Errorf(ctx, \"db query bm_contacts id=%d group=%d: %v\", contactID, groupID, err)\n    return nil, fmt.Errorf(\"load contact attribs: %w\", err)\n}","handlingStrategy":"retry","validationCode":"if err := g.DB().Ctx(ctx).Ping(ctx); err != nil {\n    return fmt.Errorf(\"database unavailable, deferring video jobs: %w\", err)\n}\n// then proceed with the attribs query","typeGuard":null,"tryCatchPattern":"attribs, err := loadContactAttribs(ctx, job.ContactID, job.GroupID)\nif err != nil {\n    var dbErr interface{ IsDBError() bool }\n    if errors.As(err, &dbErr) {\n        // transient DB issue: leave job pending, retry next poll\n        return err\n    }\n    markFailed(ctx, job.ID, \"load_contact\", err)\n    return err\n}","preventionTips":["Add DB health checks to deployment readiness probes","Set sane connection-pool limits and timeouts in GoFrame DB config","Ensure migrations create bm_contacts before the video-gen worker starts","Alert on repeated 'load contact attribs' failures — they indicate infra, not data, problems"],"tags":["database","db-query","goframe","postgres"],"backgroundTag":"database-query-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}