chenhg5/cc-connect · error

config: %s.users: user %q appears in both role %q and %q

Error message

config: %s.users: user %q appears in both role %q and %q

What it means

This validation error is returned while loading/validating the users section of a project's role-based access config. Each user_id may only be assigned to one role; duplicates (compared case-insensitively) are ambiguous so the config is rejected. It prevents silently inconsistent permission resolution where two roles would both claim the same user.

Source

Thrown at config/config.go:1182

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

// SaveActiveProvider persists the active provider name for a project.
// It uses surgical text editing to preserve comments and unknown fields.
func SaveActiveProvider(projectName, providerName string) error {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Find the role names in the message and remove the duplicate user_id from one of the two roles
  2. Keep one authoritative role per user; use default_role instead of listing the user in multiple roles
  3. Search config.toml for the user id case-insensitively to catch case-variant duplicates
  4. Reload/restart so validation re-runs and confirms the config is accepted

Example fix

// before
[[projects.users.roles.admin]]
user_ids = ["alice"]
[[projects.users.roles.member]]
user_ids = ["alice"]
// after
[[projects.users.roles.admin]]
user_ids = ["alice"]
[[projects.users.roles.member]]
user_ids = ["bob"]
Defensive patterns

Strategy: validation

Validate before calling

func validateNoDupUsers(roles map[string][]string) error {
	seen := map[string]string{}
	for role, ids := range roles {
		for _, id := range ids {
			k := strings.ToLower(id)
			if prev, ok := seen[k]; ok {
				return fmt.Errorf("user %q in %q and %q", id, prev, role)
			}
			seen[k] = role
		}
	}
	return nil
}

Try / catch

if err := cfg.Validate(); err != nil {
	if strings.Contains(err.Error(), "appears in both role") {
		// fix config before proceeding
	}
	return err
}

Prevention

When it happens

Trigger: Calling config load/validate (via the engine startup or LoadConfig) when a project's [[projects.users]] block lists the same user_id string (case-insensitive) under two different roles in u.Roles.

Common situations: Hand-editing config.toml and copy-pasting a user into a second role; a colleague already granted the user admin while you added them as member; case differences ('Alice' vs 'alice') hiding the duplicate.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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