openimsdk/open-im-server · error

user not found: %s

Error message

user not found: %s

What it means

FriendDB2Pb converts a friend DB row to its protobuf representation, which requires embedding the friend's user profile. It loads the user via getUsers; if the user map lacks the FriendUserID entry, the friend row points to a user record that no longer exists, so it fails with 'user not found: %s'.

Source

Thrown at pkg/common/convert/friend.go:48

func FriendPb2DB(friend *sdkws.FriendInfo) *model.Friend {
	dbFriend := &model.Friend{}
	err := datautil.CopyStructFields(dbFriend, friend)
	if err != nil {
		return nil
	}
	dbFriend.FriendUserID = friend.FriendUser.UserID
	dbFriend.CreateTime = timeutil.UnixSecondToTime(friend.CreateTime)
	return dbFriend
}

func FriendDB2Pb(ctx context.Context, friendDB *model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (*sdkws.FriendInfo, error) {
	users, err := getUsers(ctx, []string{friendDB.FriendUserID})
	if err != nil {
		return nil, err
	}
	user, ok := users[friendDB.FriendUserID]
	if !ok {
		return nil, fmt.Errorf("user not found: %s", friendDB.FriendUserID)
	}

	return &sdkws.FriendInfo{
		FriendUser: user,
		CreateTime: friendDB.CreateTime.Unix(),
	}, nil
}

func FriendsDB2Pb(ctx context.Context, friendsDB []*model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (friendsPb []*sdkws.FriendInfo, err error) {
	if len(friendsDB) == 0 {
		return nil, nil
	}
	var userID []string
	for _, friendDB := range friendsDB {
		userID = append(userID, friendDB.FriendUserID)
	}

	users, err := getUsers(ctx, userID)

View on GitHub (pinned to 175a7bb067)

Solutions

  1. Clean up orphaned friend rows: DELETE FROM friends WHERE friend_user_id NOT IN (SELECT user_id FROM users)
  2. Restore the missing user record if it should exist (from backup)
  3. Add cascade delete on user removal so friend rows are purged together
  4. Make getUsers/convert tolerant: skip and log missing users instead of failing the whole list

Example fix

// before
user, ok := users[friendDB.FriendUserID]
if !ok { return nil, fmt.Errorf("user not found: %s", friendDB.FriendUserID) }
// after
user, ok := users[friendDB.FriendUserID]
if !ok {
    log.ZWarn(ctx, "friend user missing, skipping", nil, "userID", friendDB.FriendUserID)
    return nil, nil // or filter upstream
}
Defensive patterns

Strategy: validation

Validate before calling

// detect orphaned friend rows before converting
orphanSQL := `SELECT f.friend_user_id FROM friends f LEFT JOIN users u ON u.user_id = f.friend_user_id WHERE u.user_id IS NULL`
rows, _ := db.Query(orphanSQL) // clean up or restore these users first

Try / catch

info, err := convert.FriendDB2Pb(ctx, friendDB)
if err != nil {
	if strings.HasPrefix(err.Error(), "user not found:") {
		log.ZWarn(ctx, "orphan friend row, skipping", err)
		return nil // skip instead of failing whole list
	}
	return err
}

Prevention

When it happens

Trigger: The friends table contains a row whose FriendUserID has no corresponding row in the users table when the friend list is fetched/converted.

Common situations: Manual DB cleanup deleted users but not their friend links; failed/partial cascade delete of a user account; data import/migration inconsistency; replication lag in read replicas.


AI-assisted analysis of openimsdk/open-im-server@175a7bb067 (2026-09-04). Data as JSON: /api/errors/ed63bc6581e614cd. Report an issue: GitHub.