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
- Verify JetStream and the '$MQTT' stream are healthy on the server
- Retry the disconnect/cleanup once the cluster is stable
- Manually delete the orphaned durable consumer (nats consumer info/rm $MQTT <dur>) if cleanup keeps failing
- 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
- Ensure JetStream is healthy during disconnects/restarts
- Don't delete or rename internal $MQTT streams
- Gracefully disconnect clients before server maintenance
- Periodically list durable consumers and remove orphans
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
- create retained messages consumer for account %q: %v
- max_request_batch must be set if it's JetStream limits are s
- consumer name can not contain '.', '*', '>', '\', '/'
- consumer durable name can not contain '.', '*', '>', '\', '/
- deliver policy can not be updated
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/ade59e60173089a4.
Report an issue: GitHub.