{"record":{"id":"904adae203ce9efc","repo":"MHSanaei/3x-ui","slug":"inbound-d-w","errorCode":null,"errorMessage":"inbound %d: %w","messagePattern":"inbound (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/web/service/client_crud.go","lineNumber":538,"sourceCode":"\tif err != nil {\n\t\treturn false, err\n\t}\n\ttombstoneClientEmail(existing.Email)\n\n\tinboundIds, err := s.GetInboundIdsForRecord(id)\n\tif err != nil {\n\t\twithdrawClientTombstones(existing.Email)\n\t\treturn false, err\n\t}\n\n\tneedRestart := false\n\tvar delErrs []error\n\tfor _, ibId := range inboundIds {\n\t\tif _, getErr := inboundSvc.GetInbound(ibId); getErr != nil {\n\t\t\tif errors.Is(getErr, gorm.ErrRecordNotFound) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdelErrs = append(delErrs, fmt.Errorf(\"inbound %d: %w\", ibId, getErr))\n\t\t\tcontinue\n\t\t}\n\n\t\t// Always delete by email — the client's stable identity. This removes\n\t\t// every matching entry from the inbound's settings even when the stored\n\t\t// credential (UUID/password/auth) drifted from the inbound JSON, or a\n\t\t// duplicate entry with the same email exists.\n\t\tif existing.Email == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tnr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, existing.Email, keepTraffic, true)\n\t\tif delErr != nil {\n\t\t\t// The client is already absent from this inbound (data drift or a\n\t\t\t// retried delete). Skip it — deletion stays idempotent.\n\t\t\tif errors.Is(delErr, ErrClientNotInInbound) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdelErrs = append(delErrs, fmt.Errorf(\"inbound %d: %w\", ibId, delErr))","sourceCodeStart":520,"sourceCodeEnd":556,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/service/client_crud.go#L520-L556","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: err: inbound 7: database is locked (5) (SQLITE_BUSY)\n// during concurrent panel writes\n\n// after: serialize the operation (retry after the other write finishes) or move to PostgreSQL for concurrent multi-admin use","handlingStrategy":"retry","validationCode":"// Pre-validate all referenced inbounds before the loop\nfor _, id := range clientInboundIds {\n    if _, err := inboundSvc.GetInbound(id); err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {\n        return fmt.Errorf(\"db unhealthy for inbound %d: %w\", id, err)\n    }\n}","typeGuard":"func isPerInboundError(err error) []error {\n    if err == nil { return nil }\n    if joined, ok := err.(interface{ Unwrap() []error }); ok {\n        return joined.Unwrap()\n    }\n    return []error{err}\n}","tryCatchPattern":"if _, err := s.UpdateClientTrafficAndStatus(...); err != nil {\n    for _, e := range isPerInboundError(err) { // log each, retry leftovers later\n        log.Printf(\"client op partial: %v\", e)\n    }\n    if dbHealthy() { return retryOnce(op) }\n    return err\n}","preventionTips":["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."],"tags":["database","gorm","client-management","sqlite"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}