hibiken/asynq · error
PeriodicTaskConfig.Cronspec cannot be empty
Error message
PeriodicTaskConfig.Cronspec cannot be empty
What it means
validatePeriodicTaskConfig checks each PeriodicTaskConfig supplied by the config provider. This error means a config had a non-nil Task but an empty Cronspec string, which asynq cannot schedule since the cron expression defines when the task runs. The manager refuses to register such a config, aborting the initial sync or periodic sync.
Solutions
- Set a valid cron expression on the config, e.g. NewPeriodicTaskConfig(task, "*/5 * * * *")
- Fix the provider/config source so the cronspec field is populated (check env var / YAML key / DB column)
- Add startup validation on your provider side to fail fast on empty specs before asynq sees them
Example fix
// before
&PeriodicTaskConfig{Task: cleanupTask}
// after
&PeriodicTaskConfig{Task: cleanupTask, Cronspec: "0 2 * * *"} Defensive patterns
Strategy: validation
Validate before calling
for i, c := range configs {
if c == nil || c.Task == nil || c.Cronspec == "" {
return fmt.Errorf("config %d invalid: nil=%v taskNil=%v emptySpec=%v", i, c == nil, c != nil && c.Task == nil, c != nil && c.Cronspec == "")
}
} Type guard
func validPeriodicConfig(c *asynq.PeriodicTaskConfig) bool {
return c != nil && c.Task != nil && c.Cronspec != ""
} Prevention
- Always construct configs via NewPeriodicTaskConfig so the cronspec is explicit
- Load cronspecs from typed config structs with required-field checks at boot
- Add a unit test that validates every entry your provider returns
When it happens
Trigger: A ConfigProvider returned a PeriodicTaskConfig whose Cronspec field is "" (never set, or set from a variable/flag that defaulted to empty). Produced by initialSync at startup or the periodic sync loop in Run.
Common situations: Loading cron specs from environment variables or config files where the key is missing; constructing PeriodicTaskConfig{Task: task} and forgetting NewPeriodicTaskConfig sets the spec; upstream config store returning blank specs.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- asynq
- asynq
- initial call to GetConfigs contained an invalid config
- asynq: no scheduler entry found
- asynq: the scheduler is already running
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/289c9dbf5eac1d5b.
Report an issue: GitHub.
Appendix: source
Thrown at periodic_task_manager.go:111
_, _ = h.Write([]byte(c.Task.Type()))
h.Write(c.Task.Payload())
opts := stringifyOptions(c.Opts)
sort.Strings(opts)
for _, opt := range opts {
_, _ = h.Write([]byte(opt))
}
return fmt.Sprintf("%x", h.Sum(nil))
}
func validatePeriodicTaskConfig(c *PeriodicTaskConfig) error {
if c == nil {
return fmt.Errorf("PeriodicTaskConfig cannot be nil")
}
if c.Task == nil {
return fmt.Errorf("PeriodicTaskConfig.Task cannot be nil")
}
if c.Cronspec == "" {
return fmt.Errorf("PeriodicTaskConfig.Cronspec cannot be empty")
}
return nil
}
// Start starts a scheduler and background goroutine to sync the scheduler with the configs
// returned by the provider.
//
// Start returns any error encountered at start up time.
func (mgr *PeriodicTaskManager) Start() error {
if mgr.s == nil || mgr.p == nil {
panic("asynq: cannot start uninitialized PeriodicTaskManager; use NewPeriodicTaskManager to initialize")
}
if err := mgr.initialSync(); err != nil {
return fmt.Errorf("asynq: %w", err)
}
if err := mgr.s.Start(); err != nil {
return fmt.Errorf("asynq: %w", err)
}View on GitHub (pinned to d135f1439b)