crowdsecurity/crowdsec · error · ParseDurationFail
decision duration '%+v
Error message
decision duration '%+v': %w: %w
What it means
CrowdSec failed to parse the `duration` field of a decision being persisted to the database. `cstime.ParseDurationWithDays` accepts Go duration strings (e.g. '4h', '30m') plus a 'd' suffix for days (e.g. '4d'); anything outside that grammar fails and the whole decision batch is rejected. The decision is not saved and the error aborts createDecisionBatch with the ParseDurationFail sentinel attached.
Solutions
- Fix the decision's Duration value to a valid Go duration or day-suffixed string ('4h', '30m', '7d') before inserting the alert
- Validate durations with cstime.ParseDurationWithDays in the producer before pushing to LAPI
- If duration may be absent, leave the field nil/empty rather than sending an unparseable placeholder string
- Inspect the quoted value in the error message to see the exact malformed input and its source (origin LAPI/parser/bouncer)
Example fix
// before
decision := &models.Decision{Duration: ptr("2 weeks"), ...}
// after
decision := &models.Decision{Duration: ptr("336h"), ...} // or "14d" Defensive patterns
Strategy: validation
Validate before calling
import "github.com/crowdsecurity/crowdsec/pkg/cstime"
if _, err := cstime.ParseDurationWithDays(dur); err != nil {
return fmt.Errorf("invalid decision duration %q: %w", dur, err)
} Try / catch
var parseErr *database.ParseDurationFail
if errors.As(err, &parseErr) {
log.Warnf("skipping decision with bad duration: %s", err)
} Prevention
- Use cstime.ParseDurationWithDays as the single validation point in any tool that builds decisions
- Never hand-format durations; derive them from time.Duration values
- Cover duration formatting in unit tests for alert producers
- Leave duration nil rather than emitting placeholder strings
When it happens
Trigger: Calling Alert.Create / alert insertion (LAPI push, cscli decisions add, bouncer feed) with a decision whose Duration pointer is non-nil but contains an unparseable string, e.g. 'forever', '', '2w', '1 y', or a float like '4.5h' when the parser doesn't accept it.
Common situations: Custom parsers/notifications or bouncers pushing hand-built decisions with human-written durations; scripts posting to LAPI with duration spelled differently than Go time.ParseDuration syntax; upstream data format changes (duration given as number of seconds instead of a duration string).
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
- query decision failed
- attach decisions to alert
- bulk creating alert
- committing alert transaction
- count all decisions with filters
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/6c7980a2840eb1b5.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/database/alerts.go:377
log.Debugf("deleted %d decisions for %s vs %s", deleted, decOrigin, *alertItem.Decisions[0].Origin)
err = txClient.Commit()
if err != nil {
return 0, 0, 0, rollbackOnError(txClient, err, "error committing transaction")
}
return alertRef.ID, inserted, deleted, nil
}
func (c *Client) createDecisionBatch(ctx context.Context, client *ent.Client, simulated bool, stopAtTime time.Time, decisions []*models.Decision) ([]*ent.Decision, error) {
decisionCreate := []*ent.DecisionCreate{}
for _, decisionItem := range decisions {
var rng csnet.Range
duration, err := cstime.ParseDurationWithDays(*decisionItem.Duration)
if err != nil {
return nil, fmt.Errorf("decision duration '%+v': %w: %w", *decisionItem.Duration, err, ParseDurationFail)
}
// if the scope is IP or Range, convert the value to integers
if strings.ToLower(*decisionItem.Scope) == "ip" || strings.ToLower(*decisionItem.Scope) == "range" {
rng, err = csnet.NewRange(*decisionItem.Value)
if err != nil {
c.Log.Errorf("invalid addr/range '%s': %s", *decisionItem.Value, err)
continue
}
}
newDecision := client.Decision.Create().
SetUntil(stopAtTime.Add(duration)).
SetScenario(*decisionItem.Scenario).
SetType(*decisionItem.Type).
SetStartIP(rng.Start.Addr).
SetStartSuffix(rng.Start.Sfx).
SetEndIP(rng.End.Addr).View on GitHub (pinned to 909b515798)