apache/answer · warning
badge already awarded
Error message
badge already awarded
What it means
Thrown when a Get on the badge_award table (user_id + badge_id + not-deleted) already returns a row, i.e. this user has already been awarded this badge. The repo refuses to insert a duplicate award. It is an intentional uniqueness guard, not a data-corruption signal.
Source
Thrown at internal/repo/badge_award/badge_award_repo.go:80
}
if !exist {
return nil, fmt.Errorf("badge not exist")
}
old := &entity.BadgeAward{
UserID: badgeAward.UserID,
BadgeID: badgeAward.BadgeID,
IsBadgeDeleted: entity.IsBadgeNotDeleted,
}
if badgeInfo.Single != entity.BadgeSingleAward {
old.AwardKey = badgeAward.AwardKey
}
exist, err = session.Get(old)
if err != nil {
return nil, err
}
if exist {
return nil, fmt.Errorf("badge already awarded")
}
_, err = session.Insert(badgeAward)
if err != nil {
return nil, err
}
return session.ID(badgeInfo.ID).Incr("award_count", 1).Update(&entity.Badge{})
})
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return nil
}
// CheckIsAward check this badge is awarded for this user or not
func (r *badgeAwardRepo) CheckIsAward(ctx context.Context, badgeID, userID, awardKey string, singleOrMulti int8) (
isAward bool, err error) {View on GitHub (pinned to 3b9f137061)
Solutions
- Check for an existing award before calling, or catch this error and treat it as success (idempotent award).
- Make the trigger/caller deduplicate: query badge_award for (user_id, badge_id) first.
- If re-award is legitimate business-wise, remove/restore the old award instead of inserting a new one.
- Add a unique index on (user_id, badge_id) so the DB enforces the same rule.
Example fix
// before
exist, err = session.Get(old)
if exist {
return nil, fmt.Errorf("badge already awarded")
}
// after
exist, err = session.Get(old)
if err != nil {
return nil, err
}
if exist {
return old, nil // idempotent: return existing award instead of error
} Defensive patterns
Strategy: validation
Validate before calling
var existing entity.BadgeAward
has, err := db.Where("user_id = ? AND badge_id = ?", userID, badgeID).Get(&existing)
if err == nil && has {
// already awarded — skip or return existing without inserting
return &existing, nil
} Type guard
func alreadyAwarded(awards []entity.BadgeAward, userID, badgeID int64) bool {
for _, a := range awards {
if a.UserID == userID && a.BadgeID == badgeID {
return true
}
}
return false
} Try / catch
award, err := badgeAwardRepo.Award(ctx, req)
if err != nil {
if strings.Contains(err.Error(), "badge already awarded") {
return nil // idempotent success
}
return err
} Prevention
- Make award operations idempotent: dedupe by (user_id, badge_id) before inserting.
- Guard queue/webhook handlers against redelivery (idempotency keys).
- Add a unique index on (user_id, badge_id).
- Do not issue the same award from both an automated trigger and a manual admin action.
When it happens
Trigger: Calling the badge-award path twice for the same (UserID, BadgeID) pair — e.g. a retry after a slow first response, or an automated trigger plus a manual award.
Common situations: Event handlers firing more than once (re-delivered webhook/queue message); admin manually granting a badge the user already earned; missing idempotency in import scripts.
Related errors
- badge not exist
- get config failed: %w
- update site info failed: %w
- update plugin status failed: %w
- connect database failed: %w
AI-assisted analysis of apache/answer@3b9f137061 (2026-09-05).
Data as JSON: /api/errors/7ed378876831f3bb.
Report an issue: GitHub.