chenhg5/cc-connect · error

user %q appears in both role %q and %q

Error message

user %q appears in both role %q and %q

What it means

ValidateRoleInputs enforces that each concrete user ID appears in at most one role. It lowercases user IDs for comparison (case-insensitive) and reports both the offending user and the two conflicting role names when a duplicate is found. This prevents ambiguous role resolution.

Source

Thrown at core/user_roles.go:223

	if len(roles) == 0 {
		return fmt.Errorf("no roles defined")
	}
	wildcardCount := 0
	seenUserIDs := make(map[string]string) // userID → role name
	roleNames := make(map[string]bool, len(roles))
	for _, ri := range roles {
		roleNames[ri.Name] = true
		if len(ri.UserIDs) == 0 {
			return fmt.Errorf("role %q has empty user_ids", ri.Name)
		}
		for _, uid := range ri.UserIDs {
			if uid == "*" {
				wildcardCount++
				continue
			}
			lower := strings.ToLower(uid)
			if prev, dup := seenUserIDs[lower]; dup {
				return fmt.Errorf("user %q appears in both role %q and %q", uid, prev, ri.Name)
			}
			seenUserIDs[lower] = ri.Name
		}
	}
	if wildcardCount > 1 {
		return fmt.Errorf("wildcard user_ids=[\"*\"] appears in multiple roles")
	}
	if defaultRole != "" {
		if !roleNames[defaultRole] {
			return fmt.Errorf("default_role %q does not match any defined role", defaultRole)
		}
	}
	return nil
}

// Stop terminates all per-role rate limiter goroutines. Nil-receiver safe.
func (m *UserRoleManager) Stop() {
	if m == nil {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Remove the user from one of the two roles named in the error
  2. Unify user ID casing across roles to make the intent explicit (matching is case-insensitive either way)
  3. Pre-process the payload in the caller to detect cross-role duplicates and surface a friendlier message

Example fix

// before
roles := []core.RoleInput{
    {Name: "admin", UserIDs: []string{"Alice"}},
    {Name: "dev", UserIDs: []string{"alice"}},
}
// after
roles := []core.RoleInput{
    {Name: "admin", UserIDs: []string{"alice"}},
    {Name: "dev", UserIDs: []string{"bob"}},
}
Defensive patterns

Strategy: validation

Validate before calling

seen := map[string]string{}
for _, r := range roles {
    for _, u := range r.UserIDs {
        k := strings.ToLower(u)
        if _, dup := seen[k]; dup {
            return fmt.Errorf("duplicate user %q", u)
        }
        seen[k] = r.Name
    }
}
core.ValidateRoleInputs(defaultRole, roles)

Prevention

When it happens

Trigger: Calling ValidateRoleInputs where two roles both list the same user ID (in any letter casing, e.g. "Alice" in role A and "alice" in role B). Wildcard "*" entries bypass this check.

Common situations: Merging two project-user configs that each assigned the same user; copy-pasting a user into a new role without removing them from the old one; a team member added to both 'admin' and 'developer'.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/f229c70fb0b287c2. Report an issue: GitHub.