{"record":{"id":"b6abec766ec09b6e","repo":"chenhg5/cc-connect","slug":"delay-must-be-positive","errorCode":null,"errorMessage":"delay must be positive","messagePattern":"delay must be positive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/timer.go","lineNumber":457,"sourceCode":"\tif _, err := rand.Read(b); err != nil {\n\t\tpanic(fmt.Errorf(\"generate timer id: %w\", err))\n\t}\n\treturn hex.EncodeToString(b)\n}\n\n// ParseDelayOrTime parses a relative duration (\"2h\", \"30m\", \"1h30m\") or\n// an absolute ISO time (\"2026-05-15T14:00\", \"2026-05-15T14:00:00+08:00\")\n// and returns the absolute fire time.\nfunc ParseDelayOrTime(s string) (time.Time, error) {\n\ts = strings.TrimSpace(s)\n\tif s == \"\" {\n\t\treturn time.Time{}, fmt.Errorf(\"empty delay or time\")\n\t}\n\n\t// Try as a Go duration first (e.g., \"2h\", \"30m\", \"1h30m\", \"2h30m15s\")\n\tif d, err := time.ParseDuration(s); err == nil {\n\t\tif d <= 0 {\n\t\t\treturn time.Time{}, fmt.Errorf(\"delay must be positive\")\n\t\t}\n\t\treturn time.Now().Add(d), nil\n\t}\n\n\t// Try ISO time formats\n\t// RFC3339 includes timezone (e.g. \"2026-05-15T14:00:00+08:00\"),\n\t// so it's parsed directly. The other layouts have no timezone\n\t// and are interpreted in the system's local timezone.\n\tlayouts := []struct {\n\t\tlayout string\n\t\tlocal  bool\n\t}{\n\t\t{time.RFC3339, false},\n\t\t{\"2006-01-02T15:04:05\", true},\n\t\t{\"2006-01-02T15:04\", true},\n\t\t{\"2006-01-02 15:04:05\", true},\n\t\t{\"2006-01-02 15:04\", true},\n\t}","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/timer.go#L439-L475","documentation":"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.","triggerScenarios":"Calling ParseDelayOrTime(\"0\"), ParseDelayOrTime(\"-5m\"), ParseDelayOrTime(\"0s\") — any string accepted by time.ParseDuration that evaluates to d <= 0.","commonSituations":"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.","solutions":["Pass a strictly positive duration, e.g. '1m' or '2h30m'.","Validate the computed duration before calling: if d <= 0, reject or bump to a minimum delay.","For absolute scheduling, use an ISO time string instead of a duration."],"exampleFix":"// before\nif err := run(\"/timer add \" + remaining); err != nil { ... } // remaining may be <= 0\n// after\nif remaining <= time.Minute {\n    remaining = time.Minute\n}\nif err := run(\"/timer add \" + remaining.String()); err != nil { ... }","handlingStrategy":"validation","validationCode":"if d, err := time.ParseDuration(arg); err == nil && d <= 0 {\n    return fmt.Errorf(\"timer delay must be positive, got %s\", arg)\n}","typeGuard":null,"tryCatchPattern":"at, err := core.ParseDelayOrTime(arg)\nif err != nil {\n    if strings.Contains(err.Error(), \"must be positive\") {\n        reply(\"Delay must be greater than zero, e.g. 30m\")\n        return\n    }\n    return err\n}","preventionTips":["Clamp computed delays to a small minimum (e.g. 1 minute).","Reject negative/zero values at the UI layer with a clear hint.","When deriving delays from timestamps, check the target is in the future first."],"tags":["timer","validation","duration"],"backgroundTag":"invalid-duration-format","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}