AdguardTeam/AdGuardHome · error

%s: must be a multiple of 1 hour

Error message

%s: must be a multiple of 1 hour

What it means

The stats HTTP handler requires the 'recent' query parameter (milliseconds) to be a whole number of hours. This error is returned when recentMs % 3600000 != 0.

Source

Thrown at internal/stats/http.go:118

// the parameter is empty, the original limit is returned.
func parseRecent(recent string, limit time.Duration) (parsedLimit time.Duration, err error) {
	if recent == "" {
		return limit, nil
	}

	recentMs, err := strconv.ParseInt(recent, 10, 64)
	if err != nil {
		return 0, fmt.Errorf("%s: parsing interval: %s", queryKeyRecent, err)
	}

	err = validate.InRange(queryKeyRecent, recentMs, millisecondsInHour, limit.Milliseconds())
	if err != nil {
		// Don't wrap the error since it's already informative enough as is.
		return 0, err
	}

	if recentMs%millisecondsInHour != 0 {
		return 0, fmt.Errorf("%s: must be a multiple of 1 hour", queryKeyRecent)
	}

	return time.Duration(recentMs) * time.Millisecond, nil
}

// configResp is the response to the GET /control/stats_info.
type configResp struct {
	IntervalDays uint32 `json:"interval"`
}

// getConfigResp is the response to the GET /control/stats_info.
type getConfigResp struct {
	// Ignored is the list of host names, which should not be counted.
	Ignored []string `json:"ignored"`

	// Interval is the statistics rotation interval in milliseconds.
	Interval float64 `json:"interval"`

View on GitHub (pinned to b41aefbe51)

Solutions

  1. Round the desired duration up/down to whole hours before sending
  2. Send e.g. recent=3600000 for 1 hour
  3. Omit recent to use the configured default limit

Example fix

// before
recent := 30 * time.Minute
// after
recent := 1 * time.Hour
Defensive patterns

Strategy: validation

Validate before calling

ms := d.Milliseconds()
ms = ms / 3600000 * 3600000 // round down to whole hours
if ms <= 0 { ms = 3600000 }

Type guard

func isWholeHours(ms int64) bool { return ms%3600000 == 0 }

Prevention

When it happens

Trigger: Calling /control/stats with a recent value that is not a multiple of one hour in milliseconds, e.g. recent=1800000 (30 minutes) or recent=1.

Common situations: Clients computing recent from arbitrary durations (minutes, seconds) or passing 1 meaning '1 hour'; stale clients from before the hourly constraint was added.

Related errors


AI-assisted analysis of AdguardTeam/AdGuardHome@b41aefbe51 (2026-08-27). Data as JSON: /api/errors/a5bc0f567096134f. Report an issue: GitHub.