{"record":{"id":"289c9dbf5eac1d5b","repo":"hibiken/asynq","slug":"periodictaskconfig-cronspec-cannot-be-empty","errorCode":null,"errorMessage":"PeriodicTaskConfig.Cronspec cannot be empty","messagePattern":"PeriodicTaskConfig\\.Cronspec cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"periodic_task_manager.go","lineNumber":111,"sourceCode":"\t_, _ = h.Write([]byte(c.Task.Type()))\n\th.Write(c.Task.Payload())\n\topts := stringifyOptions(c.Opts)\n\tsort.Strings(opts)\n\tfor _, opt := range opts {\n\t\t_, _ = h.Write([]byte(opt))\n\t}\n\treturn fmt.Sprintf(\"%x\", h.Sum(nil))\n}\n\nfunc validatePeriodicTaskConfig(c *PeriodicTaskConfig) error {\n\tif c == nil {\n\t\treturn fmt.Errorf(\"PeriodicTaskConfig cannot be nil\")\n\t}\n\tif c.Task == nil {\n\t\treturn fmt.Errorf(\"PeriodicTaskConfig.Task cannot be nil\")\n\t}\n\tif c.Cronspec == \"\" {\n\t\treturn fmt.Errorf(\"PeriodicTaskConfig.Cronspec cannot be empty\")\n\t}\n\treturn nil\n}\n\n// Start starts a scheduler and background goroutine to sync the scheduler with the configs\n// returned by the provider.\n//\n// Start returns any error encountered at start up time.\nfunc (mgr *PeriodicTaskManager) Start() error {\n\tif mgr.s == nil || mgr.p == nil {\n\t\tpanic(\"asynq: cannot start uninitialized PeriodicTaskManager; use NewPeriodicTaskManager to initialize\")\n\t}\n\tif err := mgr.initialSync(); err != nil {\n\t\treturn fmt.Errorf(\"asynq: %w\", err)\n\t}\n\tif err := mgr.s.Start(); err != nil {\n\t\treturn fmt.Errorf(\"asynq: %w\", err)\n\t}","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/hibiken/asynq/blob/d135f1439bee74e989b7f9b41ecd542cc87f024a/periodic_task_manager.go#L93-L129","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n&PeriodicTaskConfig{Task: cleanupTask}\n// after\n&PeriodicTaskConfig{Task: cleanupTask, Cronspec: \"0 2 * * *\"}","handlingStrategy":"validation","validationCode":"for i, c := range configs {\n    if c == nil || c.Task == nil || c.Cronspec == \"\" {\n        return fmt.Errorf(\"config %d invalid: nil=%v taskNil=%v emptySpec=%v\", i, c == nil, c != nil && c.Task == nil, c != nil && c.Cronspec == \"\")\n    }\n}","typeGuard":"func validPeriodicConfig(c *asynq.PeriodicTaskConfig) bool {\n    return c != nil && c.Task != nil && c.Cronspec != \"\"\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["asynq","scheduler","validation","cron"],"backgroundTag":"empty-required-field","analyzedSha":"d135f1439bee74e989b7f9b41ecd542cc87f024a","analyzedAt":"2026-09-07T19:02:34.660Z","contentChangedAt":"2026-09-07T19:02:34.660Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}