MHSanaei/3x-ui · error
inbound %d: %w
Error message
inbound %d: %w
What it means
From InboundService's multi-inbound client update/delete loop (client_crud.go): fetching one of the client's inboundIds via GetInbound failed with an error other than gorm.ErrRecordNotFound (which is skipped). The per-inbound error is collected with the inbound ID and later joined with errors.Join, so one bad inbound does not abort the rest but is reported.
Source
Thrown at internal/web/service/client_crud.go:538
if err != nil {
return false, err
}
tombstoneClientEmail(existing.Email)
inboundIds, err := s.GetInboundIdsForRecord(id)
if err != nil {
withdrawClientTombstones(existing.Email)
return false, err
}
needRestart := false
var delErrs []error
for _, ibId := range inboundIds {
if _, getErr := inboundSvc.GetInbound(ibId); getErr != nil {
if errors.Is(getErr, gorm.ErrRecordNotFound) {
continue
}
delErrs = append(delErrs, fmt.Errorf("inbound %d: %w", ibId, getErr))
continue
}
// Always delete by email — the client's stable identity. This removes
// every matching entry from the inbound's settings even when the stored
// credential (UUID/password/auth) drifted from the inbound JSON, or a
// duplicate entry with the same email exists.
if existing.Email == "" {
continue
}
nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, existing.Email, keepTraffic, true)
if delErr != nil {
// The client is already absent from this inbound (data drift or a
// retried delete). Skip it — deletion stays idempotent.
if errors.Is(delErr, ErrClientNotInInbound) {
continue
}
delErrs = append(delErrs, fmt.Errorf("inbound %d: %w", ibId, delErr))View on GitHub (pinned to ad32144c42)
Solutions
- Check the panel DB: for SQLite run the panel's built-in repair/restart, for Postgres verify connectivity and that the inbound table is readable.
- Look at the WRAPPED error after 'inbound N:' — it is the raw GORM/driver message and pinpoints the DB fault.
- Retry the client operation once DB health is restored; the loop is designed so leftovers are retried.
- If one specific inboundId always fails, inspect that row for corruption and remove the stale reference from the client's inboundIds.
Example fix
// before: err: inbound 7: database is locked (5) (SQLITE_BUSY) // during concurrent panel writes // after: serialize the operation (retry after the other write finishes) or move to PostgreSQL for concurrent multi-admin use
Defensive patterns
Strategy: retry
Validate before calling
// Pre-validate all referenced inbounds before the loop
for _, id := range clientInboundIds {
if _, err := inboundSvc.GetInbound(id); err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("db unhealthy for inbound %d: %w", id, err)
}
} Type guard
func isPerInboundError(err error) []error {
if err == nil { return nil }
if joined, ok := err.(interface{ Unwrap() []error }); ok {
return joined.Unwrap()
}
return []error{err}
} Try / catch
if _, err := s.UpdateClientTrafficAndStatus(...); err != nil {
for _, e := range isPerInboundError(err) { // log each, retry leftovers later
log.Printf("client op partial: %v", e)
}
if dbHealthy() { return retryOnce(op) }
return err
} Prevention
- Monitor DB health (SQLite locking, Postgres connectivity) before multi-inbound client ops.
- Prefer PostgreSQL when multiple admins run concurrent writes.
- Treat per-inbound errors as retriable leftovers — the loop is built for idempotent retries.
When it happens
Trigger: Updating/deleting a client whose client record references inboundIds where GetInbound returns a real DB error — connection lost to SQLite/Postgres, table locked, DB file corrupted, or a driver-level failure.
Common situations: Long-running panel operation raced a DB restart; SQLite 'database is locked' under concurrent writes; Postgres failover mid-operation; corrupted /etc/x-ui/x-ui.db after disk-full.
Related errors
- copy %T: %w
- sqlite backup is unavailable for PostgreSQL
- database is not initialized
- sqlite integrity check failed: %s
- traffic writer queue full
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/904adae203ce9efc.
Report an issue: GitHub.