XTLS/Xray-core · error
invalid VMess user
Error message
invalid VMess user
What it means
Thrown by VMessInboundConfig.Build() (processUser closure) when a raw entry of the inbound "clients"/"users" array fails to unmarshal into protocol.User. The first decode pass expects the common user envelope: "email" as string and "level" as a JSON number. A type mismatch or malformed JSON fragment makes json.Unmarshal fail and this error wraps the cause.
Source
Thrown at infra/conf/vmess.go:81
// Build implements Buildable
func (c *VMessInboundConfig) Build() (proto.Message, error) {
errors.PrintNonRemovalDeprecatedFeatureWarning("VMess (with no Forward Secrecy, etc.)", "VLESS Encryption")
config := &inbound.Config{}
if c.Defaults != nil {
config.Default = c.Defaults.Build()
}
if c.Clients != nil {
c.Users = c.Clients
}
config.User = make([]*protocol.User, len(c.Users))
processUser := func(idx int) error {
rawData := c.Users[idx]
user := new(protocol.User)
if err := json.Unmarshal(rawData, user); err != nil {
return errors.New("invalid VMess user").Base(err)
}
account := new(VMessAccount)
if err := json.Unmarshal(rawData, account); err != nil {
return errors.New("invalid VMess user").Base(err)
}
u, err := uuid.ParseString(account.ID)
if err != nil {
return err
}
account.ID = u.String()
user.Account = serial.ToTypedMessage(account.Build())
config.User[idx] = user
return nil
}
if err := task.ParallelForN(len(c.Users), processUser); err != nil {
return nil, errView on GitHub (pinned to 7d214f8b09)
Solutions
- Read the .Base(err) json cause — it names the exact field and expected type
- Ensure level is an unquoted number and email a string
- Run the config through a JSON validator (jq, jsonlint) before starting Xray
Example fix
// before
"clients": [ { "id": "...", "level": "0", "email": "u" } ]
// after
"clients": [ { "id": "...", "level": 0, "email": "u" } ] Defensive patterns
Strategy: validation
Validate before calling
func validateVMessClients(cfg map[string]any) error {
inbounds, _ := cfg["inbounds"].([]any)
for _, ib := range inbounds {
m, _ := ib.(map[string]any)
if p, _ := m["protocol"].(string); p != "vmess" { continue }
settings, _ := m["settings"].(map[string]any)
clients, _ := settings["clients"].([]any)
for i, c := range clients {
cm, _ := c.(map[string]any)
if l, ok := cm["level"]; ok {
if _, isNum := l.(float64); !isNum {
return fmt.Errorf("inbound %v: clients[%d].level must be a number", m["tag"], i)
}
}
if e, ok := cm["email"]; ok {
if _, isStr := e.(string); !isStr {
return fmt.Errorf("inbound %v: clients[%d].email must be a string", m["tag"], i)
}
}
}
}
return nil
} Type guard
func vmessClientEnvelopeOK(raw json.RawMessage) bool {
var user protocol.User
return json.Unmarshal(raw, &user) == nil
} Prevention
- level is a number, email is a string — enforce types in generators and templates
- Validate client entries with json.Unmarshal into protocol.User before deploy
- Prefer JSON (not YAML) sources, or type-aware YAML conversion, to avoid accidental quoting
When it happens
Trigger: "clients":[{"email":"a@b","level":"1"}] — level quoted; broken JSON in one client object; a nested object where a scalar is expected. Note c.Clients is aliased into c.Users before processing, so both keys are validated identically.
Common situations: YAML frontends or templates quoting all values; hand-editing client lists; importing client arrays from panels that emit stringly-typed levels.
Related errors
- unknown format of a string list:
- invalid address:
- Invalid integer range, expected either string of form "1-2"
- invalid fakedns config
- failed to parse HTTP user
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/b4a8dccd24d32eb5.
Report an issue: GitHub.