{"record":{"id":"6c7980a2840eb1b5","repo":"crowdsecurity/crowdsec","slug":"decision-duration-v-w-w","errorCode":null,"errorMessage":"decision duration '%+v': %w: %w","messagePattern":"decision duration '%\\+v': %w: %w","errorType":"exception","errorClass":"ParseDurationFail","httpStatus":null,"severity":"error","filePath":"pkg/database/alerts.go","lineNumber":377,"sourceCode":"\tlog.Debugf(\"deleted %d decisions for %s vs %s\", deleted, decOrigin, *alertItem.Decisions[0].Origin)\n\n\terr = txClient.Commit()\n\tif err != nil {\n\t\treturn 0, 0, 0, rollbackOnError(txClient, err, \"error committing transaction\")\n\t}\n\n\treturn alertRef.ID, inserted, deleted, nil\n}\n\nfunc (c *Client) createDecisionBatch(ctx context.Context, client *ent.Client, simulated bool, stopAtTime time.Time, decisions []*models.Decision) ([]*ent.Decision, error) {\n\tdecisionCreate := []*ent.DecisionCreate{}\n\n\tfor _, decisionItem := range decisions {\n\t\tvar rng csnet.Range\n\n\t\tduration, err := cstime.ParseDurationWithDays(*decisionItem.Duration)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"decision duration '%+v': %w: %w\", *decisionItem.Duration, err, ParseDurationFail)\n\t\t}\n\n\t\t// if the scope is IP or Range, convert the value to integers\n\t\tif strings.ToLower(*decisionItem.Scope) == \"ip\" || strings.ToLower(*decisionItem.Scope) == \"range\" {\n\t\t\trng, err = csnet.NewRange(*decisionItem.Value)\n\t\t\tif err != nil {\n\t\t\t\tc.Log.Errorf(\"invalid addr/range '%s': %s\", *decisionItem.Value, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tnewDecision := client.Decision.Create().\n\t\t\tSetUntil(stopAtTime.Add(duration)).\n\t\t\tSetScenario(*decisionItem.Scenario).\n\t\t\tSetType(*decisionItem.Type).\n\t\t\tSetStartIP(rng.Start.Addr).\n\t\t\tSetStartSuffix(rng.Start.Sfx).\n\t\t\tSetEndIP(rng.End.Addr).","sourceCodeStart":359,"sourceCodeEnd":395,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/database/alerts.go#L359-L395","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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)"],"exampleFix":"// before\ndecision := &models.Decision{Duration: ptr(\"2 weeks\"), ...}\n// after\ndecision := &models.Decision{Duration: ptr(\"336h\"), ...} // or \"14d\"","handlingStrategy":"validation","validationCode":"import \"github.com/crowdsecurity/crowdsec/pkg/cstime\"\nif _, err := cstime.ParseDurationWithDays(dur); err != nil {\n    return fmt.Errorf(\"invalid decision duration %q: %w\", dur, err)\n}","typeGuard":null,"tryCatchPattern":"var parseErr *database.ParseDurationFail\nif errors.As(err, &parseErr) {\n    log.Warnf(\"skipping decision with bad duration: %s\", err)\n}","preventionTips":["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"],"tags":["crowdsec","database","duration-parsing","decisions"],"backgroundTag":"invalid-duration-format","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}