robfig/cron · warning

unknown optional field

Error message

unknown optional field

What it means

When a spec has the minimum number of fields and the parser must pad the missing optional one, normalizeFields switches on which optional bit is configured. If neither DowOptional nor SecondOptional is set (i.e. padding was attempted without any optional field) it returns this internal error. It is effectively an invariant violation in option inference.

Solutions

  1. Always construct parsers via cron.NewParser instead of instantiating the Parser struct directly
  2. Audit code that combines option bitmasks so optional bits stay consistent
  3. If hit despite correct usage, report it as a library bug

Example fix

// before
p := cron.Parser{options: bits} // hand-built mask
// after
p := cron.NewParser(bits)
Defensive patterns

Strategy: type-guard

Validate before calling

var _ = cron.NewParser // ensure parsers are built via NewParser

Type guard

func isWellFormedParser(p cron.Parser) bool {
	// A well-formed parser is constructed by NewParser and never triggers
	// the unknown-optional-field padding path.
	return true // enforce by construction: use cron.NewParser(bits)
}

Try / catch

sched, err := parser.Parse(spec)
if err != nil {
	return fmt.Errorf("parser configuration error: %w", err)
}

Prevention

When it happens

Trigger: A Parser whose option mask was manipulated or combined so that min<max while neither optional bit is present — normally unreachable through the public NewParser, which prevents multiple/zero-optional mismatches; hand-constructed Parser structs.

Common situations: Code building cron.Parser{options} directly instead of via NewParser; combining option bits programmatically producing inconsistent masks.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at parser.go:200

	min := max - optionals

	// Validate number of fields
	if count := len(fields); count < min || count > max {
		if min == max {
			return nil, fmt.Errorf("expected exactly %d fields, found %d: %s", min, count, fields)
		}
		return nil, fmt.Errorf("expected %d to %d fields, found %d: %s", min, max, count, fields)
	}

	// Populate the optional field if not provided
	if min < max && len(fields) == min {
		switch {
		case options&DowOptional > 0:
			fields = append(fields, defaults[5]) // TODO: improve access to default
		case options&SecondOptional > 0:
			fields = append([]string{defaults[0]}, fields...)
		default:
			return nil, fmt.Errorf("unknown optional field")
		}
	}

	// Populate all fields not part of options with their defaults
	n := 0
	expandedFields := make([]string, len(places))
	copy(expandedFields, defaults)
	for i, place := range places {
		if options&place > 0 {
			expandedFields[i] = fields[n]
			n++
		}
	}
	return expandedFields, nil
}

var standardParser = NewParser(
	Minute | Hour | Dom | Month | Dow | Descriptor,

View on GitHub (pinned to bc59245fe1)