crowdsecurity/crowdsec · error · ParseDurationFail
decision duration '%+v': %w: %w
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.
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)
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: %w
- no database configuration provided
- unable to update
- unable to delete
- object not found
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/6c7980a2840eb1b5.
Report an issue: GitHub.