nats-io/nats-server · error

could not look up or create the retained messages stream…

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.

Solutions

  1. Retry the MQTT connection once the cluster is stable; the guard is defensive and transient races usually resolve
  2. Inspect JetStream stream list for the retained messages stream and delete a half-created/conflicting stream, then reconnect
  3. Check cluster health (jetstream cluster state) and repair any meta-layer issues before reconnecting MQTT clients
  4. 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

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


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/6ed8e3cb1ed55f3c. Report an issue: GitHub.

Appendix: 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)