nats-io/nats-server · error

unable to delete consumer %q for session %q: %v

Error message

unable to delete consumer %q for session %q: %v

What it means

Wraps an error from deleting a durable JetStream consumer associated with an MQTT session on the '$MQTT' stream. Consumer-not-found is tolerated (JSConsumerNotFoundErr is exempt); any other delete failure is wrapped with the consumer name and session ID.

Source

Thrown at server/mqtt.go:3483

	if sess.pubRelConsumer != nil {
		pubRelDur = sess.pubRelConsumer.Durable
	}

	sess.subs = nil
	sess.pendingPublish = nil
	sess.pendingPubRel = nil
	sess.cpending = nil
	sess.pubRelConsumer = nil
	sess.seq = 0
	sess.tmaxack = 0
	// Discarded session: reset the PI counter too so a reused session object
	// does not inherit the previous session's identifiers. Spec [MQTT-3.1.2-6].
	sess.last_pi = 0
	sess.mu.Unlock()

	for _, dur := range durs {
		if _, err := sess.jsa.deleteConsumer(mqttStreamName, dur, noWait); isErrorOtherThan(err, JSConsumerNotFoundErr) {
			return fmt.Errorf("unable to delete consumer %q for session %q: %v", dur, sess.id, err)
		}
	}
	if pubRelDur != _EMPTY_ {
		_, err := sess.jsa.deleteConsumer(mqttOutStreamName, pubRelDur, noWait)
		if isErrorOtherThan(err, JSConsumerNotFoundErr) {
			return fmt.Errorf("unable to delete consumer %q for session %q: %v", pubRelDur, sess.id, err)
		}
	}

	if seq > 0 {
		err := sess.jsa.deleteMsg(mqttSessStreamName, seq, !noWait)
		// Ignore the various errors indicating that the message (or sequence)
		// is already deleted, can happen in a cluster.
		if isErrorOtherThan(err, JSSequenceNotFoundErrF) {
			if isErrorOtherThan(err, JSStreamMsgDeleteFailedF) || !strings.Contains(err.Error(), ErrStoreMsgNotFound.Error()) {
				return fmt.Errorf("unable to delete session %q record at sequence %v: %v", id, seq, err)
			}
		}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Verify JetStream and the '$MQTT' stream are healthy on the server
  2. Retry the disconnect/cleanup once the cluster is stable
  3. Manually delete the orphaned durable consumer (nats consumer info/rm $MQTT <dur>) if cleanup keeps failing
  4. Check for clock/latency issues causing delete timeouts

Example fix

// before
// disconnect fails: unable to delete consumer
// after
nats con rm $MQTT <durable-name>  // manual cleanup after restoring JetStream health
null
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check that the stream and its consumers are reachable
const streams = await fetch('http://monitor:8222/jsz?consumers=true').then(r=>r.json())
const mqttStream = streams.account_details?.flatMap(a=>a.streams||[]).find(s=>s.name==='$MQTT')
if (!mqttStream) console.warn('$MQTT stream missing; disconnect cleanup will fail')

Try / catch

try {
  await client.disconnect()
} catch (e) {
  if (String(e).includes('unable to delete consumer')) {
    // orphaned durable; clean up manually once JetStream is healthy
    await natsAdmin.consumerDelete('$MQTT', durableName)
  } else { throw e }
}

Prevention

When it happens

Trigger: sess.jsa.deleteConsumer(mqttStreamName, dur, noWait) returns an error other than JSConsumerNotFoundErr during session teardown — timeout, no responders, stream missing, or API failure.

Common situations: JetStream unavailable during client disconnect; cluster quorum lost; stream '$MQTT' deleted or renamed; slow/no-wait delete racing with a busy server.

Related errors


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