{"record":{"id":"6dbc7692554ca285","repo":"hibiken/asynq","slug":"cannot-encode-nil-scheduler-entry","errorCode":null,"errorMessage":"cannot encode nil scheduler entry","messagePattern":"cannot encode nil scheduler entry","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/base/base.go","lineNumber":511,"sourceCode":"\n\t// Payload is the payload of the periodic task.\n\tPayload []byte\n\n\t// Opts is the options for the periodic task.\n\tOpts []string\n\n\t// Next shows the next time the task will be enqueued.\n\tNext time.Time\n\n\t// Prev shows the last time the task was enqueued.\n\t// Zero time if task was never enqueued.\n\tPrev time.Time\n}\n\n// EncodeSchedulerEntry marshals the given entry and returns an encoded bytes.\nfunc EncodeSchedulerEntry(entry *SchedulerEntry) ([]byte, error) {\n\tif entry == nil {\n\t\treturn nil, fmt.Errorf(\"cannot encode nil scheduler entry\")\n\t}\n\tnext := timestamppb.New(entry.Next)\n\tprev := timestamppb.New(entry.Prev)\n\n\treturn proto.Marshal(&pb.SchedulerEntry{\n\t\tId:              entry.ID,\n\t\tSpec:            entry.Spec,\n\t\tTaskType:        entry.Type,\n\t\tTaskPayload:     entry.Payload,\n\t\tEnqueueOptions:  entry.Opts,\n\t\tNextEnqueueTime: next,\n\t\tPrevEnqueueTime: prev,\n\t})\n}\n\n// DecodeSchedulerEntry unmarshals the given bytes and returns a decoded SchedulerEntry.\nfunc DecodeSchedulerEntry(b []byte) (*SchedulerEntry, error) {\n\tvar pbmsg pb.SchedulerEntry","sourceCodeStart":493,"sourceCodeEnd":529,"githubUrl":"https://github.com/hibiken/asynq/blob/d135f1439bee74e989b7f9b41ecd542cc87f024a/internal/base/base.go#L493-L529","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Nil-check each entry before calling EncodeSchedulerEntry and skip nil entries.","Ensure the code creating SchedulerEntry values always returns a valid struct or an error, never a bare nil pointer.","If entries come from a registry/map, handle missing keys explicitly instead of storing nil."],"exampleFix":"// before\nfor _, e := range entries {\n    b, _ := base.EncodeSchedulerEntry(e) // panics/errors on nil\n}\n// after\nfor _, e := range entries {\n    if e == nil {\n        continue\n    }\n    b, err := base.EncodeSchedulerEntry(e)\n    if err != nil {\n        return err\n    }\n}","handlingStrategy":"validation","validationCode":"if entry == nil {\n    return errors.New(\"refusing to encode nil scheduler entry\")\n}","typeGuard":"func encodableEntry(e *base.SchedulerEntry) bool { return e != nil && e.ID != \"\" && !e.Spec.IsZero() }","tryCatchPattern":"b, err := base.EncodeSchedulerEntry(entry)\nif err != nil {\n    return fmt.Errorf(\"encode scheduler entry %s: %w\", entryID, err)\n}","preventionTips":["Skip nil entries when iterating scheduler-entry collections.","Make entry producers return (entry, error) and never (nil, nil).","Cover entry construction branches with unit tests."],"tags":["go","nil-pointer","serialization","scheduler"],"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"}