MHSanaei/3x-ui · warning · ErrClientNotInInbound
%w for email: %s
Error message
%w for email: %s
What it means
Returned when removing a client from an inbound's settings JSON by email: after filtering the settings' clients array, no entry with that email was found, so the code refuses with ErrClientNotInInbound ('... for email: <email>'). The inbound's stored settings do not contain the client the caller expects.
Source
Thrown at internal/web/service/client_inbound_apply.go:936
var newClients []any
needApiDel := false
found := false
for _, client := range interfaceClients {
c, ok := client.(map[string]any)
if !ok {
continue
}
if cEmail, ok := c["email"].(string); ok && cEmail == email {
found = true
needApiDel, _ = c["enable"].(bool)
} else {
newClients = append(newClients, client)
}
}
if !found {
return false, fmt.Errorf("%w for email: %s", ErrClientNotInInbound, email)
}
db := database.GetDB()
newClients = compactOrphans(db, newClients)
if newClients == nil {
newClients = []any{}
}
settings["clients"] = newClients
newSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return false, err
}
prevSettings := oldInbound.Settings
oldInbound.Settings = string(newSettings)
emailShared, err := inboundSvc.emailUsedByOtherInbounds(email, inboundId)
if err != nil {
return false, errView on GitHub (pinned to ad32144c42)
Solutions
- Callers should treat this error as benign/idempotent — the delete loops already skip it; if you call it directly, do the same with errors.Is(err, ErrClientNotInInbound).
- If the client SHOULD be there, inspect the inbound's settings JSON and the client record for email drift (spaces, case, duplicate entries).
- Re-save the client on that inbound to re-sync settings JSON with the client table.
- Trim/normalize emails on input so storage never accumulates whitespace variants.
Example fix
// before: treating it as a hard failure
err := s.DelInboundClientByEmail(inboundSvc, ibId, email, keepTraffic, true)
if err != nil { return err }
// after: skip the idempotent not-present case
if err != nil && !errors.Is(err, service.ErrClientNotInInbound) {
return err
} Defensive patterns
Strategy: validation
Validate before calling
// Normalize and check presence before deleting from an inbound
email = strings.ToLower(strings.TrimSpace(email))
if !inboundHasClient(ib, email) {
return nil // nothing to remove: treat as success
} Type guard
func IsClientNotInInbound(err error) bool {
return errors.Is(err, service.ErrClientNotInInbound)
} Try / catch
if _, err := s.DelInboundClientByEmail(inboundSvc, ibId, email, keepTraffic, true); err != nil {
if errors.Is(err, service.ErrClientNotInInbound) {
return false, nil // already absent — idempotent success
}
return false, err
} Prevention
- Always skip ErrClientNotInInbound when calling the delete-by-email API directly.
- Normalize emails (trim, case policy) at every entry point to prevent drift variants.
- Re-save a client on its inbound after restoring/migrating DBs to re-sync settings JSON.
When it happens
Trigger: Calling DelInboundClientByEmail (directly or via the multi-inbound delete loops) with an email that is not present in that inbound's settings.clients — client already removed, email case/whitespace mismatch, or the client only exists in a client-record table but not in this inbound's JSON.
Common situations: Data drift between the clients table and inbound settings JSON (legacy upgrades, hand edits); retried deletes where the first attempt already succeeded; trailing space or different case in the email.
Related errors
- The new username and password are empty
- ❌ Failed to get inbounds.
- tg_id must be a positive integer
- token not found
- node port must be 1-65535
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/50549df9db145ca3.
Report an issue: GitHub.