robfig/cron · critical

multiple optionals may not be configured

Error message

multiple optionals may not be configured

What it means

NewParser panics when more than one optional field bit (SecondOptional, DowOptional) is set in the option mask. The parser can only handle one optional field because the normalization logic can only pad one missing field. It is a programming error, so it panics instead of returning an error.

Solutions

  1. Remove one of SecondOptional or DowOptional from the NewParser option mask
  2. Use two separate Parser instances if both flexible forms are needed
  3. Gate the option construction on configuration so only one optional bit can ever be set

Example fix

// before
p := cron.NewParser(cron.SecondOptional | cron.DowOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Descriptor)
// after
p := cron.NewParser(cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Descriptor)
Defensive patterns

Strategy: validation

Validate before calling

func validateParserOptions(opts uint64) error {
	optionals := 0
	if opts&cron.SecondOptional > 0 { optionals++ }
	if opts&cron.DowOptional > 0 { optionals++ }
	if optionals > 1 { return fmt.Errorf("at most one optional field bit allowed") }
	return nil
}

Prevention

When it happens

Trigger: Calling NewParser with option flags combining both SecondOptional and DowOptional (e.g. NewParser(SecondOptional|DowOptional|Minute|Hour|...)).

Common situations: Developers copying parser option examples add both optional bits to support flexible 5/6-field cron specs, not realizing they are mutually exclusive. Merging feature flags from two call sites can also accumulate both bits.

Related errors


AI-assisted analysis of robfig/cron@bc59245fe1 (2026-09-07). Data as JSON: /api/errors/61b00fe0dc3572b7. Report an issue: GitHub.

Appendix: source

Thrown at parser.go:80

//
//  // Same as above, just excludes time fields
//  specParser := NewParser(Dom | Month | Dow)
//  sched, err := specParser.Parse("15 */3 *")
//
//  // Same as above, just makes Dow optional
//  specParser := NewParser(Dom | Month | DowOptional)
//  sched, err := specParser.Parse("15 */3")
//
func NewParser(options ParseOption) Parser {
	optionals := 0
	if options&DowOptional > 0 {
		optionals++
	}
	if options&SecondOptional > 0 {
		optionals++
	}
	if optionals > 1 {
		panic("multiple optionals may not be configured")
	}
	return Parser{options}
}

// Parse returns a new crontab schedule representing the given spec.
// It returns a descriptive error if the spec is not valid.
// It accepts crontab specs and features configured by NewParser.
func (p Parser) Parse(spec string) (Schedule, error) {
	if len(spec) == 0 {
		return nil, fmt.Errorf("empty spec string")
	}

	// Extract timezone if present
	var loc = time.Local
	if strings.HasPrefix(spec, "TZ=") || strings.HasPrefix(spec, "CRON_TZ=") {
		var err error
		i := strings.Index(spec, " ")
		eq := strings.Index(spec, "=")

View on GitHub (pinned to bc59245fe1)