{"record":{"id":"dc526e6037088bda","repo":"hibiken/asynq","slug":"periodictaskconfig-task-cannot-be-nil","errorCode":null,"errorMessage":"PeriodicTaskConfig.Task cannot be nil","messagePattern":"PeriodicTaskConfig\\.Task cannot be nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"periodic_task_manager.go","lineNumber":108,"sourceCode":"func (c *PeriodicTaskConfig) hash() string {\n\th := sha256.New()\n\t_, _ = h.Write([]byte(c.Cronspec))\n\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}","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/hibiken/asynq/blob/d135f1439bee74e989b7f9b41ecd542cc87f024a/periodic_task_manager.go#L90-L126","documentation":"validatePeriodicTaskConfig returns this error when a PeriodicTaskConfig has a nil Task field. A config without a Task has nothing to enqueue, so the sync loop rejects it. Raised during initialSync/sync for each offending provider-supplied config.","triggerScenarios":"A PeriodicTaskConfigProvider returns a config where only Cronspec (and maybe Options) is set but the Task field is left nil.","commonSituations":"Constructing configs from persisted spec strings and forgetting to rebuild the asynq.Task; conditional task construction that yields nil on a code path; copy-paste of a config literal missing the Task line.","solutions":["Always populate Task with a non-nil *asynq.Task in every config","Add a pre-return validation loop in the provider mirroring validatePeriodicTaskConfig","Fail fast in the provider when a spec cannot be mapped to a Task"],"exampleFix":"// before\nreturn []*asynq.PeriodicTaskConfig{\n    {Cronspec: \"0 6 * * *\", Options: []asynq.Option{}}, // Task missing\n}, nil\n// after\nreturn []*asynq.PeriodicTaskConfig{\n    {Task: asynq.NewTask(\"report:generate\", nil), Cronspec: \"0 6 * * *\"},\n}, nil","handlingStrategy":"validation","validationCode":"for i, c := range cfgs {\n    if c != nil && c.Task == nil {\n        return fmt.Errorf(\"config %d (%s) has nil Task\", i, c.Cronspec)\n    }\n}","typeGuard":"func hasTask(c *asynq.PeriodicTaskConfig) bool { return c != nil && c.Task != nil }","tryCatchPattern":"if err := mgr.Run(); err != nil {\n    if strings.Contains(err.Error(), \"PeriodicTaskConfig.Task cannot be nil\") {\n        // fix provider to always set Task\n    }\n    return err\n}","preventionTips":["Construct every PeriodicTaskConfig with asynq.NewTask(...)","Add a validate loop in the provider mirroring library checks","Cover config construction with table-driven tests","Reject incomplete specs at the persistence layer"],"tags":["validation","nil-check","periodic-tasks"],"backgroundTag":"null-argument","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"}