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

  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.