nats-io/nats-server · error
could not look up the retained messages stream for account %
Error message
could not look up the retained messages stream for account %q
What it means
Similar to the nil guard earlier: after the retained-messages transfer step (transferRMS) ran, the server checks si before reading si.Config and found it nil, meaning the stream could not be resolved even after transfer/retry. This indicates the retained messages stream for the account is simply not available.
Source
Thrown at server/mqtt.go:1565
// to load all retained messages.
si, err = lookupStream(mqttRetainedMsgsStreamName, "retained messages")
if err != nil {
return err
}
needToTransfer = false
return nil
}
// Attempt to transfer all "single subject" retained messages to new
// subjects. It may fail, will log its own error; ignore it the first time
// and proceed to updating MaxMsgsPer. Then we invoke transferRMS() again,
// which will get another chance to resolve the error; if not we bail there.
if err = transferRMS(); err != nil {
return nil, err
}
// Guard before dereferencing si.Config below.
if si == nil {
return nil, fmt.Errorf("could not look up the retained messages stream for account %q", accName)
}
// Now, if the stream does not have MaxMsgsPer set to 1, and there are no
// more messages on the single $MQTT.rmsgs subject, update the stream again.
if si.Config.MaxMsgsPer != 1 {
si.Config.MaxMsgsPer = 1
// We will need an up-to-date si, so don't use local variable here.
if si, err = jsa.updateStream(&si.Config); err != nil {
return nil, fmt.Errorf("failed to update stream config: %w", err)
}
}
// If we failed the first time, there is now at most one lingering message
// in the old subject. Try again (it will be a NO-OP if succeeded the first
// time).
if err = transferRMS(); err != nil {
return nil, err
}View on GitHub (pinned to 3a66a489d2)
Solutions
- Verify the stream exists: nats stream ls and look for the retained messages stream; reconnect MQTT clients so the server recreates it
- If the stream was deleted, disconnect/reconnect the MQTT session to trigger recreation
- Check cluster/meta health and logs for lookup failures
- Ensure no ops scripts delete JetStream streams used internally by the MQTT subsystem
Defensive patterns
Strategy: retry
Validate before calling
// Confirm the internal stream exists and is healthy:
js, _ := nc.JetStream()
si, err := js.StreamInfo("$MQTT.rmsgs")
if err == nil {
for _, r := range si.State.Replicas { /* check peer lag/health */ }
} Type guard
func streamAvailable(si *nats.StreamInfo) bool { return si != nil } Try / catch
if strings.Contains(err.Error(), "could not look up the retained messages stream") {
// stream deleted or lookup raced; reconnect to trigger recreation
return retryReconnect(ctx)
} Prevention
- Protect internal streams from ops deletion (document $MQTT.* ownership)
- Reconnect MQTT clients after any manual stream surgery
- Monitor JS stream existence as part of health checks when MQTT is in use
When it happens
Trigger: transferRMS returned success but the lookup inside it still failed to populate si, or the stream was deleted concurrently between creation/update and this check; the retained messages stream does not exist for the account.
Common situations: Someone deleted the $MQTT.rmsgs stream while MQTT clients were connected; cluster instability causing lookups to fail; half-completed upgrade/migration of the retained messages stream.
Related errors
- lookup %s stream for account %q: %v
- could not look up or create the retained messages stream for
- 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/8548f96f8711e381.
Report an issue: GitHub.