Skip to content

Errors

Every sentinel is declared with errors.NewSentinel and carries a namespaced kind, so its identity survives crossing a process boundary and it captures no stack at package initialisation.

Sentinel Kind Returned when
ErrNetworkModeUnsupported nats.network_mode_unsupported An embedded server would listen or mesh
ErrCredentialInURL nats.credential_in_url A URL carries user information
ErrInsecureCredential nats.insecure_credential A credential would cross an unencrypted scheme
ErrUnboundedSubscription nats.unbounded_subscription A pending limit is not positive
ErrNotConnected nats.not_connected A client is not connected: not yet, reconnecting, or closed
ErrNoStoreDir nats.no_store_dir JetStream is set with no StoreDir
ErrNoClusterName nats.no_cluster_name Routes is set with no ClusterName
ErrNoTarget nats.no_target A client has neither a URL nor an in-process server
ErrAmbiguousTarget nats.ambiguous_target A client has both
ErrNotReady nats.not_ready An embedded server did not accept connections in time
ErrUnbounded nats.unbounded A stream or bucket would be created with no limit

Why these are refusals rather than defaults

Each one is a case where a sensible-looking default would fail later, quietly, somewhere else.

A listening server would accept anything that could reach it: no TLS, no authentication, and a client's token neither required nor protected. Refusing means the unsafe surface does not exist.

A credential in a URL puts a secret in a struct that gets logged, dumped and committed.

A credential over nats:// is bearer material in plaintext. Anyone on the path has it.

A non-positive pending limit means unlimited to NATS, which is the unbounded queue the bound exists to prevent.

A JetStream server with no store directory writes where it pleases. That differs by host, so the first time it matters is a restart that lost data on one node and not the others.

A client with two targets has been told two different things. Picking one is how a service ends up talking to a broker nobody thought it was talking to.

ErrNotConnected is not always a failure

Start returns once the client is trying, not once it has connected — the initial connect uses the same retry policy as every reconnect. So "not yet" is an ordinary state, and WaitReady is how a caller waits for it to stop being true.

It is also what a publish returns while the connection is down. A publish during a reconnect is refused, not held: nats.go would otherwise accept it into an 8 MiB buffer and return nil for a message that has not been sent, losing the lot if that buffer fills. A bound nobody set is a shed nobody sees, and that applies on the write path too.

This sentinel therefore covers not-yet, reconnecting and closed. Readiness and Liveness distinguish them: reconnecting is not ready but is alive; closed is neither.

ErrUnbounded is what the module owns about JetStream

Client.Stream, Client.KeyValue and Client.ObjectStore refuse anything created with no MaxAge, MaxMsgs, MaxBytes or TTL.

Every JetStream limit defaults to unlimited, and NATS' own documentation warns that this exhausts storage — so a stream created without one is bounded by the volume rather than by anything a person chose, and the failure arrives at whatever hour the volume fills.

A caller can still reach JetStream through Client.Raw() and create whatever it likes. The refusal is a guardrail on the path this module offers, not a cage.