chenhg5/cc-connect · error

config: .users: user appears in both role and

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.

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.

Appendix: 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)