goharbor/harbor · error · errors.Error
BAD_REQUEST
BAD_REQUEST
Error message
invalid cron string for scheduled preheat: %s, error: %v
What it means
Schema.ValidatePreheatPolicy rejects a P2P preheat policy whose trigger is scheduled but whose cron string is invalid. Harbor parses cron with a 6-field parser (second minute hour day-of-month month day-of-week) and additionally requires the first field (seconds) to be the literal 0 — so both malformed strings and non-zero seconds fail. Maps to BAD_REQUEST when creating/updating policies (POST/PUT /api/v2.0/projects/{project}/preheat/policies).
Source
Thrown at src/pkg/p2p/preheat/models/policy/policy.go:126
// TriggerType represents the type of trigger.
type TriggerType = string
// Trigger holds the trigger info.
type Trigger struct {
// The preheat policy trigger type. The valid values ar manual, scheduled.
Type TriggerType `json:"type"`
Settings struct {
// The cron string for scheduled trigger.
Cron string `json:"cron,omitempty"`
} `json:"trigger_setting,omitempty"`
}
// ValidatePreheatPolicy validate preheat policy
func (s *Schema) ValidatePreheatPolicy() error {
// currently only validate cron string of preheat policy
if s.Trigger != nil && s.Trigger.Type == TriggerTypeScheduled && len(s.Trigger.Settings.Cron) > 0 {
if err := utils.ValidateCronString(s.Trigger.Settings.Cron); err != nil {
return errors.New(nil).WithCode(errors.BadRequestCode).
WithMessagef("invalid cron string for scheduled preheat: %s, error: %v", s.Trigger.Settings.Cron, err)
}
}
return nil
}
// Encode encodes policy schema.
func (s *Schema) Encode() error {
if s.Filters != nil {
filterStr, err := json.Marshal(s.Filters)
if err != nil {
return err
}
s.FiltersStr = string(filterStr)
}
if s.Trigger != nil {View on GitHub (pinned to 7b2fd08cc5)
Solutions
- Use exactly 6 fields with a leading 0 for seconds, e.g. "0 */5 * * * *" fires every 5 minutes.
- Validate the string with the same rules client-side: 6 fields, parseable, first field == "0".
- If the policy only needs manual or event-based preheat, use trigger type "manual"/"event" instead of "scheduled".
Example fix
# before
curl -X POST https://harbor/api/v2.0/projects/1/preheat/policies -d '{"name": "p", "provider_id": 1, "trigger": {"type": "scheduled", "trigger_setting": {"cron": "*/5 * * * *"}}}'
# after
curl -X POST https://harbor/api/v2.0/projects/1/preheat/policies -d '{"name": "p", "provider_id": 1, "trigger": {"type": "scheduled", "trigger_setting": {"cron": "0 */5 * * * *"}}}' Defensive patterns
Strategy: validation
Validate before calling
// Replicate Harbor's cron rules before submitting a scheduled policy
func validPreheatCron(c string) error {
if len(c) == 0 { return errors.New("cron string is required") }
if _, err := utils.CronParser().Parse(c); err != nil { return err } // 6 fields: sec min hour dom mon dow
p := strings.Split(c, " ")
if len(p) == 6 && p[0] != "0" { return errors.New("seconds field must be 0") }
return nil
} Type guard
function isScheduledPolicyWithBadCron(p) {
return p?.trigger?.type === 'scheduled' && !validCron6(p.trigger.trigger_setting?.cron);
} Try / catch
On HTTP 400 'invalid cron string for scheduled preheat', parse the returned error detail, coerce the cron to 6 fields with seconds=0, and resubmit the policy once.
Prevention
- Memorize the Harbor rule: 6 fields, first field (seconds) must be literal 0.
- Never paste 5-field crontab entries directly into preheat policies.
- Add a cron-lint step to policy-as-code pipelines.
When it happens
Trigger: POST/PUT preheat policy with trigger {"type": "scheduled", "trigger_setting": {"cron": "*/5 * * * *"}} — a classic 5-field crontab string; or 6 fields with a non-zero seconds field like "30 */5 * * * *"; or invalid ranges/step syntax per robfig/cron.
Common situations: Copying standard 5-field crontab entries from Linux docs; assuming Harbor uses the same cron dialect as Dragonfly/Kraken; harbor replicate-schedule muscle memory where seconds are allowed; step values on day-of-week names.
Related errors
- File {} not exist
- Internal dir for tls {} not exist
- Port number in metrics is not valid
- Jaeger Colector Endpoint or Agent host not set, must set one
- Jaeger Colector Endpoint and Agent host both set, only can s
AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16).
Data as JSON: /api/errors/717f7fe506299b06.
Report an issue: GitHub.