hibiken/asynq · critical
asynq: cannot start uninitialized PeriodicTaskManager; use…
Error message
asynq: cannot start uninitialized PeriodicTaskManager; use NewPeriodicTaskManager to initialize
What it means
PeriodicTaskManager.Start panics when the manager's internal scheduler (mgr.s) or provider (mgr.p) is nil, i.e. the struct was created with plain &PeriodicTaskManager{} or via a zero-value composite literal instead of the required NewPeriodicTaskManager constructor. The library requires construction via NewPeriodicTaskManager, which wires the scheduler and provider, so Start on an uninitialized instance is a programming error.
Solutions
- Construct with asynq.NewPeriodicTaskManager(asynq.PeriodicTaskManagerOpts{RedisConnOpt: ..., Provider: ...}) and use the returned pointer
- Use mgr.Run() instead of calling Start manually if you want start+stop handled together
- Check that the constructor's error return is handled and the non-nil result is stored
- Never copy the PeriodicTaskManager value; always pass and store the *PeriodicTaskManager pointer
Example fix
// before
var mgr asynq.PeriodicTaskManager
mgr.Start()
// after
mgr, err := asynq.NewPeriodicTaskManager(asynq.PeriodicTaskManagerOpts{
RedisConnOpt: asynq.RedisClientOpt{Addr: ":6379"},
Provider: myConfigProvider,
})
if err != nil { log.Fatal(err) }
if err := mgr.Run(); err != nil { log.Fatal(err) } Defensive patterns
Strategy: validation
Validate before calling
if mgr == nil {
return errors.New("PeriodicTaskManager not constructed; use NewPeriodicTaskManager")
}
if err := mgr.Run(); err != nil { log.Fatal(err) } Type guard
func managerReady(mgr *asynq.PeriodicTaskManager) bool {
return mgr != nil
} Prevention
- Always construct with NewPeriodicTaskManager; never use a zero-value PeriodicTaskManager
- Store the returned *PeriodicTaskManager pointer; do not copy the struct by value
- Handle the constructor's error and only Start/Run a non-nil manager
- Prefer mgr.Run() over manual Start/Stop pairing
When it happens
Trigger: Calling Start on a PeriodicTaskManager built as &asynq.PeriodicTaskManager{} or asynq.PeriodicTaskManager{}; declaring the manager as a struct field and using it before assigning the result of NewPeriodicTaskManager; copying the manager value so internal fields end up nil.
Common situations: DI/manual wiring skipping the constructor; refactoring where NewPeriodicTaskManager's error return was dropped and the nil manager was kept; embedding the manager by value in another struct causing a partial copy.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- asynq: unsupported RedisConnOpt type %T
- inspeq: unsupported RedisConnOpt type %T
- asynq: unsupported RedisConnOpt type %T
- asynq: invalid pattern
- asynq: nil handler
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/90aafee46a556852.
Report an issue: GitHub.
Appendix: source
Thrown at periodic_task_manager.go:122
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)
}
mgr.wg.Add(1)
go func() {
defer mgr.wg.Done()
ticker := time.NewTicker(mgr.syncInterval)
for {
select {
case <-mgr.done:
mgr.s.logger.Debugf("Stopping syncer goroutine")
ticker.Stop()
return
case <-ticker.C:View on GitHub (pinned to d135f1439b)