{"record":{"id":"258410f66b7c9782","repo":"plandex-ai/plandex","slug":"error-adding-org-member-v","errorCode":null,"errorMessage":"error adding org member: %v","messagePattern":"error adding org member: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/org_helpers.go","lineNumber":203,"sourceCode":"\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error deleting org member: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc CreateOrgUser(orgId, userId, orgRoleId string, tx *sqlx.Tx) error {\n\tquery := \"INSERT INTO orgs_users (org_id, user_id, org_role_id) VALUES ($1, $2, $3)\"\n\tvar err error\n\tif tx == nil {\n\t\t_, err = Conn.Exec(query, orgId, userId, orgRoleId)\n\t} else {\n\t\t_, err = tx.Exec(query, orgId, userId, orgRoleId)\n\t}\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error adding org member: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc ListOrgRoles(orgId string) ([]*OrgRole, error) {\n\tvar orgRoles []*OrgRole\n\terr := Conn.Select(&orgRoles, \"SELECT * FROM org_roles WHERE org_id IS NULL OR org_id = $1\", orgId)\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error listing org roles: %v\", err)\n\t}\n\n\treturn orgRoles, nil\n}\n\nfunc AddToOrgForDomain(userId, domain string, tx *sqlx.Tx) (string, error) {\n\torg, err := GetOrgForDomain(domain)","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/org_helpers.go#L185-L221","documentation":"CreateOrgUser wraps any failure of the INSERT INTO orgs_users (org_id, user_id, org_role_id) statement with this message. It fires when the INSERT itself fails — most commonly a duplicate (org_id,user_id) row violating the org_user_unique constraint, a missing org_role_id foreign key, or a DB connection error.","triggerScenarios":"Calling CreateOrgUser with (a) a user already in the org (unique constraint violation on orgs_users), (b) an invalid orgRoleId that doesn't exist in org_roles (FK violation), (c) an invalid orgId or userId, or (d) the database connection is down.","commonSituations":"Auto-adding a user to a domain-matched org when the user already exists as a member; passing a role id from a different org; race where two requests add the same user concurrently; stale DB credentials or a restarted Postgres.","solutions":["Check the wrapped cause in the %v output: 'duplicate key value violates unique constraint \"org_user_unique\"' means the user is already a member — treat as success or use an ON CONFLICT DO NOTHING clause like AddUsersToOrgForDomain does","Verify the orgRoleId exists in org_roles (fetch via GetOrgOwnerRoleId/GetOrgMemberRoleId) before inserting","Verify orgId and userId reference existing rows in orgs and users","If the DB connection failed, check DATABASE_URL connectivity and retry after the pool recovers"],"exampleFix":"// before\nerr = CreateOrgUser(org.Id, userId, orgOwnerRoleId, tx)\n// after: tolerate existing membership\nquery := \"INSERT INTO orgs_users (org_id, user_id, org_role_id) VALUES ($1, $2, $3) ON CONFLICT ON CONSTRAINT org_user_unique DO NOTHING\"","handlingStrategy":"validation","validationCode":"// check membership before inserting\nvar exists bool\nerr := Conn.Get(&exists, \"SELECT EXISTS(SELECT 1 FROM orgs_users WHERE org_id=$1 AND user_id=$2)\", orgId, userId)\nif err == nil && exists {\n    return nil // already a member\n}\n// also: rows, _ := Conn.Queryx(\"SELECT id FROM org_roles WHERE id=$1\", orgRoleId); ensure role exists","typeGuard":"func orgRoleExists(roleId string) (bool, error) {\n\tvar ok bool\n\terr := Conn.Get(&ok, \"SELECT EXISTS(SELECT 1 FROM org_roles WHERE id=$1)\", roleId)\n\treturn ok, err\n}","tryCatchPattern":"if err := CreateOrgUser(orgId, userId, roleId, tx); err != nil {\n    if strings.Contains(err.Error(), \"org_user_unique\") {\n        return nil // idempotent: already a member\n    }\n    return fmt.Errorf(\"adding org member %s to %s: %w\", userId, orgId, err)\n}","preventionTips":["Use INSERT ... ON CONFLICT ON CONSTRAINT org_user_unique DO NOTHING for idempotent membership","Always source orgRoleId from GetOrgOwnerRoleId/GetOrgMemberRoleId, never hard-code","Validate org and user ids exist before inserting","Run within a transaction to avoid duplicate-insert races"],"tags":["database","postgres","insert","unique-constraint","go"],"backgroundTag":"postgres-unique-constraint-violation","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}