ory/hydra · error
cookiex: max age must not be negative
Error message
cookiex: max age must not be negative
What it means
cookiex.New validates its option-configured settings before constructing the cookie codec. When an option (e.g. WithMaxAge) sets a negative MaxAge value, New refuses to build the codec and returns this error, because cookies with negative lifetimes are invalid/expired-on-issue.
Source
Thrown at oryx/cookiex/cookiex.go:95
// ciphertext and used as the metric label; it must be a short constant like
// "kratos/session". Because the purpose is embedded in the additional
// authenticated data, it must be non-empty and must not contain a pipe
// character. The codec seals with a key derived from the first secret
// and opens with keys derived from any of them, so secrets rotate by
// prepending a new one.
func New[T any](purpose string, secrets [][]byte, opts ...Option) (*Codec[T], error) {
if purpose == "" || strings.Contains(purpose, "|") {
return nil, errors.New("cookiex: purpose must be non-empty and must not contain a pipe character")
}
if len(secrets) == 0 {
return nil, errors.New("cookiex: at least one secret is required")
}
cfg := config{maxAge: defaultMaxAge}
for _, opt := range opts {
opt(&cfg)
}
if cfg.maxAge < 0 {
return nil, errors.New("cookiex: max age must not be negative")
}
if cfg.legacyEncode && len(cfg.legacyKeyPairs) == 0 {
return nil, errors.New("cookiex: legacy encode requires legacy key pairs")
}
keys := make([][32]byte, len(secrets))
for i, secret := range secrets {
key, err := hkdf.Key(sha256.New, secret, nil, kdfInfo, 32)
if err != nil {
return nil, errors.Wrap(err, "cookiex: cannot derive key")
}
keys[i] = [32]byte(key)
}
return &Codec[T]{
purpose: purpose,
keys: keys,
maxAge: cfg.maxAge,
legacy: newLegacyState(cfg, cfg.maxAge),
now: time.Now,View on GitHub (pinned to 4174065ffb)
Solutions
- Use a non-negative MaxAge value in the option (e.g. WithMaxAge(3600) or a positive time.Duration).
- To make a session cookie (no explicit expiry), omit the WithMaxAge option and rely on the library default (defaultMaxAge) or the zero/session-cookie path instead of passing -1.
- Validate configured durations from env/config before passing them to New.
- Wrap New at startup so this fail-fast error surfaces at boot, not at first cookie write.
Example fix
// before
cc, err := cookiex.New([][]byte{secret}, cookiex.WithMaxAge(-1))
// after
cc, err := cookiex.New([][]byte{secret}, cookiex.WithMaxAge(24*3600)) Defensive patterns
Strategy: validation
Validate before calling
func validateMaxAge(d int) error {
if d < 0 {
return fmt.Errorf("max age must be >= 0, got %d", d)
}
return nil
}
// call validateMaxAge(cfg.MaxAge) before cookiex.New(...) Try / catch
cfg, err := cookiex.New(secrets, opts...)
if err != nil {
return nil, fmt.Errorf("cookiex init: %w", err) // fail fast at boot
} Prevention
- Never pass -1 as MaxAge; omit the option for defaults
- Sanity-check durations parsed from env/config before use
- Construct cookiex once at startup so errors surface immediately
When it happens
Trigger: Calling cookiex.New(secrets...) with WithMaxAge(-1) (or any negative duration/seconds value) in the options slice.
Common situations: Typo in a time constant (e.g. time.Duration(-1) instead of omitting), computing a duration from a config value that parsed as negative, or passing a 'no expiry' sentinel of -1 that this library does not accept.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- cookiex: purpose must be non-empty and must not contain a pi
- cookiex: at least one secret is required
- cookiex: legacy encode requires legacy key pairs
- the provided region is not a valid Ory region
- a key ID must be specified when multiple JWK sets are config
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/d993bf7d3a323d3d.
Report an issue: GitHub.