hibiken/asynq · error

cannot encode nil scheduler entry

Error message

cannot encode nil scheduler entry

What it means

EncodeSchedulerEntry marshals a *SchedulerEntry (a periodic-task registration with Next/Prev times) into protobuf bytes for persistence. The library throws this when the entry pointer is nil because a nil entry has no ID, spec, or timestamps to encode. It guards proto.Marshal against a nil dereference.

Solutions

  1. Nil-check each entry before calling EncodeSchedulerEntry and skip nil entries.
  2. Ensure the code creating SchedulerEntry values always returns a valid struct or an error, never a bare nil pointer.
  3. If entries come from a registry/map, handle missing keys explicitly instead of storing nil.

Example fix

// before
for _, e := range entries {
    b, _ := base.EncodeSchedulerEntry(e) // panics/errors on nil
}
// after
for _, e := range entries {
    if e == nil {
        continue
    }
    b, err := base.EncodeSchedulerEntry(e)
    if err != nil {
        return err
    }
}
Defensive patterns

Strategy: validation

Validate before calling

if entry == nil {
    return errors.New("refusing to encode nil scheduler entry")
}

Type guard

func encodableEntry(e *base.SchedulerEntry) bool { return e != nil && e.ID != "" && !e.Spec.IsZero() }

Try / catch

b, err := base.EncodeSchedulerEntry(entry)
if err != nil {
    return fmt.Errorf("encode scheduler entry %s: %w", entryID, err)
}

Prevention

When it happens

Trigger: Calling base.EncodeSchedulerEntry(nil) directly, or code that iterates scheduler entries and writes each one where the slice contains a nil pointer (e.g. from a failed entry construction or a lookup miss).

Common situations: Custom periodic-task managers syncing entries to Redis where an entry lookup returns nil; tests building SchedulerEntry slices with placeholder nils; refactor changing entry construction so a struct literal assignment is skipped on some branch.

Related errors


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

Appendix: source

Thrown at internal/base/base.go:511

	// Payload is the payload of the periodic task.
	Payload []byte

	// Opts is the options for the periodic task.
	Opts []string

	// Next shows the next time the task will be enqueued.
	Next time.Time

	// Prev shows the last time the task was enqueued.
	// Zero time if task was never enqueued.
	Prev time.Time
}

// EncodeSchedulerEntry marshals the given entry and returns an encoded bytes.
func EncodeSchedulerEntry(entry *SchedulerEntry) ([]byte, error) {
	if entry == nil {
		return nil, fmt.Errorf("cannot encode nil scheduler entry")
	}
	next := timestamppb.New(entry.Next)
	prev := timestamppb.New(entry.Prev)

	return proto.Marshal(&pb.SchedulerEntry{
		Id:              entry.ID,
		Spec:            entry.Spec,
		TaskType:        entry.Type,
		TaskPayload:     entry.Payload,
		EnqueueOptions:  entry.Opts,
		NextEnqueueTime: next,
		PrevEnqueueTime: prev,
	})
}

// DecodeSchedulerEntry unmarshals the given bytes and returns a decoded SchedulerEntry.
func DecodeSchedulerEntry(b []byte) (*SchedulerEntry, error) {
	var pbmsg pb.SchedulerEntry

View on GitHub (pinned to d135f1439b)