nats-io/nats-server · error · JSStreamInvalidConfigError
max age can not be negative
Error message
max age can not be negative
What it means
JetStream rejects a stream config whose MaxAge (max message retention duration) is negative. MaxAge must be zero (use stream default) or a positive duration; this is a hard JSStreamInvalidConfigError regardless of pedantic mode.
Source
Thrown at server/stream.go:1951
if pedantic && cfg.MaxBytes < -1 {
return StreamConfig{}, NewJSPedanticError(fmt.Errorf("max_bytes must be set to -1"))
}
cfg.MaxBytes = -1
}
if cfg.MaxMsgSize == 0 || cfg.MaxMsgSize < -1 {
if pedantic && cfg.MaxMsgSize < -1 {
return StreamConfig{}, NewJSPedanticError(fmt.Errorf("max_msg_size must be set to -1"))
}
cfg.MaxMsgSize = -1
}
if cfg.MaxConsumers == 0 || cfg.MaxConsumers < -1 {
if pedantic && cfg.MaxConsumers < -1 {
return StreamConfig{}, NewJSPedanticError(fmt.Errorf("max_consumers must be set to -1"))
}
cfg.MaxConsumers = -1
}
if cfg.MaxAge < 0 {
return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("max age can not be negative"))
}
if cfg.MaxAge != 0 && cfg.MaxAge < 100*time.Millisecond {
return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("max age needs to be >= 100ms"))
}
if cfg.Duplicates == 0 && cfg.Mirror == nil && len(cfg.Sources) == 0 {
maxWindow := StreamDefaultDuplicatesWindow
if lim.Duplicates > 0 && maxWindow > lim.Duplicates {
if pedantic {
return StreamConfig{}, NewJSPedanticError(fmt.Errorf("duplicate window limits are higher than current limits"))
}
maxWindow = lim.Duplicates
}
if cfg.MaxAge != 0 && cfg.MaxAge < maxWindow {
if pedantic {
return StreamConfig{}, NewJSPedanticError(fmt.Errorf("duplicate window cannot be bigger than max age"))
}
cfg.Duplicates = cfg.MaxAgeView on GitHub (pinned to 3a66a489d2)
Solutions
- Fix the duration computation so MaxAge is non-negative (e.g. 30*time.Minute)
- Set MaxAge to 0 to use the server default retention window
- Clamp with a guard: if maxAge < 0 { maxAge = 0 } before submitting
Example fix
// before
MaxAge: time.Until(deadline) // deadline already passed -> negative
// after
if d := time.Until(deadline); d > 0 { cfg.MaxAge = d } else { cfg.MaxAge = 0 } Defensive patterns
Strategy: validation
Validate before calling
if cfg.MaxAge < 0 {
return fmt.Errorf("MaxAge cannot be negative; got %v", cfg.MaxAge)
} Try / catch
if err != nil {
var apiErr *nats.APIError
if errors.As(err, &apiErr) && apiErr.ErrorCode == nats.ErrorCodeJetStreamInvalidStreamConfig {
// log the raw MaxAge value for diagnosis, then fix or clamp
cfg.MaxAge = 0
err = createStream(cfg)
}
} Prevention
- Clamp durations to >= 0 after any time computation
- Guard against 'deadline in the past' producing negative time.Until results
- Use typed duration parsing helpers instead of raw string-to-duration casts
When it happens
Trigger: Stream create/update with cfg.MaxAge < 0, e.g. time.Duration computed as -30*time.Minute or an unparsed/wrong-unit value yielding a negative duration. server/stream.go:1951.
Common situations: Retention computed from a deadline in the past (now - expiry); config parsing where a string like '-1h' was passed; arithmetic on time.Duration in the wrong unit producing overflow/negative values.
Related errors
- max age needs to be >= 100ms
- max_expires must not be negative
- stream republish transform from '%s' to '%s': %w
- maximum replicas is %d
- max_msgs must be set to -1
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/2fbbdea2c417083a.
Report an issue: GitHub.