{"record":{"id":"b67af4475c89a874","repo":"goharbor/harbor","slug":"bad-request-b67af4","errorCode":"BAD_REQUEST","errorMessage":"invalid cron string for scheduled tag retention: %s, error: %v","messagePattern":"invalid cron string for scheduled tag retention: (.+?), error: (.+?)","errorType":"validation","errorClass":"lib/errors.Error","httpStatus":400,"severity":"error","filePath":"src/pkg/retention/policy/models.go","lineNumber":73,"sourceCode":"\n\t// Trigger about how to launch the policy\n\tTrigger *Trigger `json:\"trigger\" valid:\"Required\"`\n\n\t// Which scope the policy will be applied to\n\tScope *Scope `json:\"scope\" valid:\"Required\"`\n}\n\n// ValidateRetentionPolicy validate the retention policy\nfunc (m *Metadata) ValidateRetentionPolicy() error {\n\t// currently only validate the cron string of retention policy\n\tif m.Trigger != nil {\n\t\tif m.Trigger.Kind == TriggerKindSchedule && m.Trigger.Settings != nil {\n\t\t\tcronItem, ok := m.Trigger.Settings[TriggerSettingsCron]\n\t\t\tif ok {\n\t\t\t\tif cronStr, isStr := cronItem.(string); isStr {\n\t\t\t\t\tif len(cronStr) > 0 {\n\t\t\t\t\t\tif err := utils.ValidateCronString(cronStr); err != nil {\n\t\t\t\t\t\t\treturn errors.New(nil).WithCode(errors.BadRequestCode).\n\t\t\t\t\t\t\t\tWithMessagef(\"invalid cron string for scheduled tag retention: %s, error: %v\", cronStr, err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn errors.New(nil).WithCode(errors.BadRequestCode).\n\t\t\t\t\t\tWithMessage(\"invalid cron type for scheduled tag retention: must be a string\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// Valid Valid\nfunc (m *Metadata) Valid(v *validation.Validation) {\n\tif m.Trigger == nil {\n\t\t_ = v.SetError(\"Trigger\", \"Can not be empty\")\n\t\treturn","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/pkg/retention/policy/models.go#L55-L91","documentation":"BAD_REQUEST error from Metadata.ValidateRetentionPolicy when a policy's trigger is of kind Schedule, its settings contain the cron key, and the string fails utils.ValidateCronString. It is pure input validation fired at policy create/update time, before any scheduling happens.","triggerScenarios":"POST/PUT of a retention policy with Trigger.Kind == Schedule and Settings[\"cron\"] set to an invalid expression (wrong field count, bad ranges like '0 25 * * *', unsupported macros); using a 5-field expression where Harbor's validator expects a specific format or vice versa.","commonSituations":"Copy-pasting cron strings from crontab (5 fields) or Quartz (7 fields) formats that don't match what ValidateCronString accepts; typos in day/month names; setting cron with leading/trailing whitespace.","solutions":["Correct the cron string to a valid expression accepted by Harbor's validator (e.g. '0 0 0 * * *' style 6-field)","Run the same validation client-side before submitting, e.g. with an equivalent cron parser, to fail fast","Use '@daily'/'@weekly' style aliases if supported instead of hand-written fields"],"exampleFix":"// before\nsettings := map[string]any{\"cron\": \"99 99 * * *\"}\n\n// after\nsettings := map[string]any{\"cron\": \"0 0 0 * * 6\"}","handlingStrategy":"validation","validationCode":"// Fail fast on cron syntax before policy submission\nif err := utils.ValidateCronString(cronStr); err != nil {\n    return fmt.Errorf(\"bad cron %q: %v\", cronStr, err)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reuse the same cron library/version Harbor validates with on the client side","Prefer fixed known-good expressions ('0 0 0 * * *') over hand-built ones","Test cron strings in a scratch policy before automating them fleet-wide"],"tags":["retention","cron","validation","bad-request"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}