chenhg5/cc-connect · error

delay must be positive

Error message

delay must be positive

What it means

When ParseDelayOrTime's input parses successfully as a Go duration (time.ParseDuration succeeds) but the duration is zero or negative, the function rejects it with 'delay must be positive'. A timer scheduled in the past or exactly now would never usefully fire, so non-positive delays are invalid.

Source

Thrown at core/timer.go:457

	if _, err := rand.Read(b); err != nil {
		panic(fmt.Errorf("generate timer id: %w", err))
	}
	return hex.EncodeToString(b)
}

// ParseDelayOrTime parses a relative duration ("2h", "30m", "1h30m") or
// an absolute ISO time ("2026-05-15T14:00", "2026-05-15T14:00:00+08:00")
// and returns the absolute fire time.
func ParseDelayOrTime(s string) (time.Time, error) {
	s = strings.TrimSpace(s)
	if s == "" {
		return time.Time{}, fmt.Errorf("empty delay or time")
	}

	// Try as a Go duration first (e.g., "2h", "30m", "1h30m", "2h30m15s")
	if d, err := time.ParseDuration(s); err == nil {
		if d <= 0 {
			return time.Time{}, fmt.Errorf("delay must be positive")
		}
		return time.Now().Add(d), nil
	}

	// Try ISO time formats
	// RFC3339 includes timezone (e.g. "2026-05-15T14:00:00+08:00"),
	// so it's parsed directly. The other layouts have no timezone
	// and are interpreted in the system's local timezone.
	layouts := []struct {
		layout string
		local  bool
	}{
		{time.RFC3339, false},
		{"2006-01-02T15:04:05", true},
		{"2006-01-02T15:04", true},
		{"2006-01-02 15:04:05", true},
		{"2006-01-02 15:04", true},
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Pass a strictly positive duration, e.g. '1m' or '2h30m'.
  2. Validate the computed duration before calling: if d <= 0, reject or bump to a minimum delay.
  3. For absolute scheduling, use an ISO time string instead of a duration.

Example fix

// before
if err := run("/timer add " + remaining); err != nil { ... } // remaining may be <= 0
// after
if remaining <= time.Minute {
    remaining = time.Minute
}
if err := run("/timer add " + remaining.String()); err != nil { ... }
Defensive patterns

Strategy: validation

Validate before calling

if d, err := time.ParseDuration(arg); err == nil && d <= 0 {
    return fmt.Errorf("timer delay must be positive, got %s", arg)
}

Try / catch

at, err := core.ParseDelayOrTime(arg)
if err != nil {
    if strings.Contains(err.Error(), "must be positive") {
        reply("Delay must be greater than zero, e.g. 30m")
        return
    }
    return err
}

Prevention

When it happens

Trigger: Calling ParseDelayOrTime("0"), ParseDelayOrTime("-5m"), ParseDelayOrTime("0s") — any string accepted by time.ParseDuration that evaluates to d <= 0.

Common situations: User typo like '-30m' instead of '30m'; computing a remaining delay from a stale timestamp that has already elapsed yielding zero or negative; a scripted command passing '0' as a placeholder.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/b6abec766ec09b6e. Report an issue: GitHub.