#1132·asynq

Panic in user-provided callbacks (HealthCheckFunc, GroupAggregator, PeriodicTaskConfigProvider) crashes worker process

Author: blackwell-systemsCreated May 14, 2026Updated May 14, 2026

Problem

Three user-provided callbacks are called inside library goroutines without defer recover(). A panic in any of them kills the entire worker process, terminating all in-flight tasks.

Affected callbacks

Callback File Called in goroutine at
HealthCheckFunc healthcheck.go:75 healthcheck.go:64
GroupAggregator.Aggregate aggregator.go:161 aggregator.go:122 (go a.aggregate(t))
PeriodicTaskConfigProvider.GetConfigs periodic_task_manager.go:209 periodic_task_manager.go:131

Example: common HealthCheckFunc bug

go
srv := asynq.NewServer(redisOpt, asynq.Config{
    HealthCheckFunc: func(err error) {
        // Bug: err is nil when ping succeeds
        metrics.Record("health", err.Error()) // PANIC: nil pointer dereference
    },
})

The library calls HealthCheckFunc(nil) when the broker ping succeeds. The user's code dereferences err.Error() on nil. The goroutine panics. The entire worker process dies. All in-flight tasks are lost.

Why this matters

processor.perform() already recovers from panics in task handlers (processor.go:424). The same principle applies to these other user-facing interfaces: the library spawns the goroutine, the library calls the user's code, so the library is responsible for recovering if that code panics.