hibiken/asynq · error

cannot encode nil enqueue event

Error message

cannot encode nil enqueue event

What it means

EncodeSchedulerEnqueueEvent marshals a *SchedulerEnqueueEvent (a record that a periodic task enqueued a task, with EnqueuedAt timestamp) into protobuf bytes for persistence in Redis. The library throws this when the event pointer is nil, since there is nothing to encode. It protects proto.Marshal from a nil dereference.

Solutions

  1. Check err and the pointer before encoding: only call EncodeSchedulerEnqueueEvent with a non-nil event.
  2. Fix the event producer to always return a populated event or a non-nil error.
  3. Skip/log nil events in bulk-write loops instead of attempting to encode them.

Example fix

// before
ev, _ := newEnqueueEvent(task) // error ignored, ev may be nil
b, err := base.EncodeSchedulerEnqueueEvent(ev)
// after
ev, err := newEnqueueEvent(task)
if err != nil {
    return err
}
if ev == nil {
    return nil // or log and skip
}
b, err := base.EncodeSchedulerEnqueueEvent(ev)
Defensive patterns

Strategy: validation

Validate before calling

if event == nil {
    return errors.New("refusing to encode nil enqueue event")
}

Type guard

func validEvent(e *base.SchedulerEnqueueEvent) bool { return e != nil && e.TaskID != "" }

Try / catch

b, err := base.EncodeSchedulerEnqueueEvent(ev)
if err != nil {
    return fmt.Errorf("encode enqueue event: %w", err)
}

Prevention

When it happens

Trigger: Calling base.EncodeSchedulerEnqueueEvent(nil) directly, or passing the result of an event-producing function that returned nil on a failure branch without checking it first.

Common situations: Custom event loggers that record periodic-task enqueue events and pass through values from a function returning (*Event, error) but ignoring the error; test fixtures with uninitialized event pointers.

Related errors


AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07). Data as JSON: /api/errors/96e99797a07d9e2c. Report an issue: GitHub.

Appendix: source

Thrown at internal/base/base.go:560

		Next:    next.AsTime(),
		Prev:    prev.AsTime(),
	}, nil
}

// SchedulerEnqueueEvent holds information about an enqueue event by a scheduler.
type SchedulerEnqueueEvent struct {
	// ID of the task that was enqueued.
	TaskID string

	// Time the task was enqueued.
	EnqueuedAt time.Time
}

// EncodeSchedulerEnqueueEvent marshals the given event
// and returns an encoded bytes.
func EncodeSchedulerEnqueueEvent(event *SchedulerEnqueueEvent) ([]byte, error) {
	if event == nil {
		return nil, fmt.Errorf("cannot encode nil enqueue event")
	}
	enqueuedAt := timestamppb.New(event.EnqueuedAt)
	return proto.Marshal(&pb.SchedulerEnqueueEvent{
		TaskId:      event.TaskID,
		EnqueueTime: enqueuedAt,
	})
}

// DecodeSchedulerEnqueueEvent unmarshals the given bytes
// and returns a decoded SchedulerEnqueueEvent.
func DecodeSchedulerEnqueueEvent(b []byte) (*SchedulerEnqueueEvent, error) {
	var pbmsg pb.SchedulerEnqueueEvent
	if err := proto.Unmarshal(b, &pbmsg); err != nil {
		return nil, err
	}
	enqueuedAt := pbmsg.GetEnqueueTime()
	return &SchedulerEnqueueEvent{
		TaskID:     pbmsg.GetTaskId(),

View on GitHub (pinned to d135f1439b)