nats-io/nats-server · error
could not look up or create the retained messages stream for
Error message
could not look up or create the retained messages stream for account %q
What it means
After attempting to create or look up the retained messages stream, the server found its StreamInfo pointer nil, meaning neither creation nor lookup produced a usable stream. This is a defensive guard against a race where createStream returned 'already exists' but the subsequent lookupStream also failed silently in the code path, leaving no stream info to dereference.
Source
Thrown at server/mqtt.go:1525
if err != nil {
if isErrorOtherThan(err, JSStreamNameExistErr) {
return nil, fmt.Errorf("create retained messages stream for account %q: %v", accName, err)
}
// Suppose we had a race and the stream was actually created by another
// node, we really need "si" after that, so lookup the stream again here.
si, err = lookupStream(mqttRetainedMsgsStreamName, "retained messages")
if err != nil {
return nil, err
}
}
needToTransfer = false
default:
needToTransfer = si.Config.MaxMsgsPer != 1
}
// Guard before dereferencing si.Config below.
if si == nil {
return nil, fmt.Errorf("could not look up or create the retained messages stream for account %q", accName)
}
// Doing this check outside of above if/else due to possible race when
// creating the stream.
wantedSubj := mqttRetainedMsgsStreamSubject + ">"
if len(si.Config.Subjects) != 1 || si.Config.Subjects[0] != wantedSubj {
// Update only the Subjects at this stage, not MaxMsgsPer yet.
si.Config.Subjects = []string{wantedSubj}
if si, err = jsa.updateStream(&si.Config); err != nil {
return nil, fmt.Errorf("failed to update stream config: %w", err)
}
}
transferRMS := func() error {
if !needToTransfer {
return nil
}
View on GitHub (pinned to 3a66a489d2)
Solutions
- Retry the MQTT connection once the cluster is stable; the guard is defensive and transient races usually resolve
- Inspect JetStream stream list for the retained messages stream and delete a half-created/conflicting stream, then reconnect
- Check cluster health (jetstream cluster state) and repair any meta-layer issues before reconnecting MQTT clients
- Update to a recent NATS server version; this race handling has been hardened over releases
Defensive patterns
Strategy: retry
Validate before calling
// Ensure stream is discoverable before MQTT use:
js, _ := nc.JetStream()
_, err := js.StreamInfo("$MQTT.rmsgs")
// err == ErrStreamNotFound is fine pre-connect; anything else investigate Type guard
func hasStreamInfo(si *nats.StreamInfo) bool { return si != nil && si.Config.Name != "" } Try / catch
if err != nil && strings.Contains(err.Error(), "could not look up or create the retained messages stream") {
// transient race in cluster; retry after delay
time.Sleep(2 * time.Second)
return reconnectMQTT()
} Prevention
- Avoid simultaneous first-time MQTT connects across many clients in a fresh cluster; stagger them
- Keep the cluster meta layer healthy (odd number of JS peers)
- Do not delete internal $MQTT streams while clients are connected
When it happens
Trigger: A race between server nodes: one node's createStream hits JSStreamNameExistErr, then the follow-up lookupStream fails/returns nil so si remains nil when the code proceeds to read si.Config; essentially stream neither created nor found.
Common situations: Clustered NATS servers racing to serve MQTT clients for the same account simultaneously; JetStream meta-layer inconsistency after a cluster split or restart; transient JS API failures during lookup.
Related errors
- lookup %s stream for account %q: %v
- could not look up the retained messages stream for account %
- ack wait must be a positive value
- JS API timeout must be a positive value
- mqtt requires JetStream to be enabled if running in standalo
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/6ed8e3cb1ed55f3c.
Report an issue: GitHub.