XTLS/Xray-core · error
User ${email} already exists.
Error message
User ${email} already exists. What it means
Validator.Add rejected a trojan user because another user with the same email (compared lowercased) is already registered in this inbound. Emails are the user-facing key for management APIs (stats, del), so uniqueness (case-insensitive) is enforced at add time.
Source
Thrown at proxy/trojan/validator.go:23
"sync"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
)
// Validator stores valid trojan users.
type Validator struct {
// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.
email sync.Map
users sync.Map
}
// Add a trojan user, Email must be empty or unique.
func (v *Validator) Add(u *protocol.MemoryUser) error {
if u.Email != "" {
_, loaded := v.email.LoadOrStore(strings.ToLower(u.Email), u)
if loaded {
return errors.New("User ", u.Email, " already exists.")
}
}
v.users.Store(hexString(u.Account.(*MemoryAccount).Key), u)
return nil
}
// Del a trojan user with a non-empty Email.
func (v *Validator) Del(e string) error {
if e == "" {
return errors.New("Email must not be empty.")
}
le := strings.ToLower(e)
u, _ := v.email.Load(le)
if u == nil {
return errors.New("User ", e, " not found.")
}
v.email.Delete(le)
v.users.Delete(hexString(u.(*protocol.MemoryUser).Account.(*MemoryAccount).Key))View on GitHub (pinned to 7d214f8b09)
Solutions
- Remove or rename the duplicate email in the inbound's clients list before reloading
- If driving via API, list existing users first and skip/replace instead of re-adding
- Remember comparison is case-insensitive: 'A@x' collides with 'a@x'
Example fix
// before
"clients": [
{"password": "p1", "email": "user@x"},
{"password": "p2", "email": "user@x"}
]
// after
"clients": [
{"password": "p1", "email": "user@x"},
{"password": "p2", "email": "user2@x"}
] Defensive patterns
Strategy: validation
Validate before calling
// before adding, check for the (case-insensitive) email collision
func canAdd(v *Validator, email string) bool {
if email == "" { return true }
_, exists := v.email.Load(strings.ToLower(email))
return !exists
} Try / catch
if err := validator.Add(user); err != nil {
if strings.Contains(err.Error(), "already exists") {
// idempotent provisioning: treat as success or update-in-place
return nil
}
return err
} Prevention
- Deduplicate the clients array in config before reload (CI lint or pre-flight script)
- Make API-driven provisioning idempotent: check-then-add keyed on lowercased email
- Never reuse one email across users; emails drive stats and deletion lookups
When it happens
Trigger: Calling the trojan inbound's Add with an email that exists — typically via the API handler (proxy.AddInboundHandler / handler.service) or config reload that merges duplicate client entries.
Common situations: Copy-pasted 'clients' entries with the same email in config; API-driven user provisioning retrying after a partial success; email differing only by case ('Alice' vs 'alice').
Related errors
- Shadowsocks password is not specified.
- Trojan settings: "servers" should have one and only one memb
- Trojan server address is not set.
- Invalid Trojan port.
- Trojan password is not specified.
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/2fadce0aa748908d.
Report an issue: GitHub.