MHSanaei/3x-ui · warning

tg_id must be a positive integer

Error message

tg_id must be a positive integer

What it means

GetRecordsByTgID validates that the Telegram ID argument is a positive (>0) int64 before querying client_records by tg_id. Zero and negatives are rejected because tg_id=0 would match every record that has no linked Telegram account, returning a meaningless result set; negative IDs never exist in Telegram. Callers parsing user input should treat this as a validation failure, not a DB error.

Source

Thrown at internal/web/service/client_lookup.go:109

func (s *ClientService) GetInboundIdsForEmail(tx *gorm.DB, email string) ([]int, error) {
	if tx == nil {
		tx = database.GetDB()
	}
	var ids []int
	err := tx.Table("client_inbounds").
		Select("client_inbounds.inbound_id").
		Joins("JOIN clients ON clients.id = client_inbounds.client_id").
		Where("clients.email = ?", email).
		Scan(&ids).Error
	if err != nil {
		return nil, err
	}
	return ids, nil
}

func (s *ClientService) GetRecordsByTgID(tgId int64) ([]*model.ClientRecord, error) {
	if tgId <= 0 {
		return nil, errors.New("tg_id must be a positive integer")
	}
	var rows []*model.ClientRecord
	err := database.GetDB().Where("tg_id = ?", tgId).Find(&rows).Error
	return rows, err
}

func (s *ClientService) GetByID(id int) (*model.ClientRecord, error) {
	row := &model.ClientRecord{}
	if err := database.GetDB().Where("id = ?", id).First(row).Error; err != nil {
		return nil, err
	}
	return row, nil
}

func (s *ClientService) GetInboundIdsForRecord(id int) ([]int, error) {
	var ids []int
	err := database.GetDB().Table("client_inbounds").
		Where("client_id = ?", id).

View on GitHub (pinned to ad32144c42)

Solutions

  1. Check the parse error before calling: if err := ...; err != nil or value <= 0, return 'invalid tg_id' to the caller
  2. Default to a not-found response rather than 0 when the parameter is absent
  3. For 'unassigned' queries, use a dedicated IsNull/'tg_id IS NULL' query instead of tg_id=0

Example fix

// before
tgId, _ := strconv.ParseInt(raw, 10, 64)
rows, err := svc.GetRecordsByTgID(tgId)

// after
tgId, err := strconv.ParseInt(raw, 10, 64)
if err != nil || tgId <= 0 {
    return fmt.Errorf("invalid telegram id: %q", raw)
}
rows, err := svc.GetRecordsByTgID(tgId)
Defensive patterns

Strategy: validation

Validate before calling

tgId, err := strconv.ParseInt(raw, 10, 64)
if err != nil || tgId <= 0 {
    return nil, fmt.Errorf("invalid telegram id %q", raw)
}

Try / catch

rows, err := svc.GetRecordsByTgID(tgId)
if err != nil {
    if strings.Contains(err.Error(), "positive integer") {
        // input problem: reject the request, do not retry
    }
    return err
}

Prevention

When it happens

Trigger: Calling GetRecordsByTgID(0) when input parsing failed and the error was ignored (the strconv result is often discarded); passing -1 from a misparsed unsigned value; looking up records before any client has that tg_id linked (0 used as 'missing' sentinel).

Common situations: Code paths doing tgId, _ := strconv.ParseInt(...) and proceeding on failure; API handlers defaulting to 0.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/ed4ca011f3c16cbf. Report an issue: GitHub.