nats-io/nats-server · error

stream assignment error: %w

Error message

stream assignment error: %w

What it means

During a JetStream health check (jsAllStreamsHealthz-style), a stream assignment persisted in the meta layer carries a stored error (sa.err) — e.g. the stream failed to be created on this peer due to account limits. Instead of running runtime-only checks against a broken/absent stream, the health check surfaces this assignment error so the stream reports unhealthy. The message wraps the original cause via %w.

Source

Thrown at server/jetstream_cluster.go:1020

		return nil
	}
	s, cc := js.srv, js.cluster
	if cc == nil {
		// Non-clustered mode
		js.mu.RUnlock()
		return nil
	}
	if sa == nil || sa.Group == nil {
		js.mu.RUnlock()
		return errors.New("stream assignment or group missing")
	}
	// Surface any persisted assignment-level error (e.g. failed create on this
	// peer due to account limits) so the health check reflects the broken state
	// instead of falling through to runtime-only checks.
	if sa.err != nil {
		err := sa.err
		js.mu.RUnlock()
		return fmt.Errorf("stream assignment error: %w", err)
	}
	streamName := sa.Config.Name
	node := sa.Group.node
	js.mu.RUnlock()

	// First lookup stream and make sure its there.
	mset, err := acc.lookupStream(streamName)
	if err != nil {
		return errors.New("stream not found")
	}

	msetNode := mset.raftNode()
	mset.cfgMu.RLock()
	replicas := mset.cfg.Replicas
	mset.cfgMu.RUnlock()
	var nrgWerr error
	if node != nil {
		nrgWerr = node.GetWriteErr()

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Inspect the wrapped cause (errors.Unwrap / %w chain) to find the underlying failure (e.g. account limits) and remediate it (raise limits, free storage)
  2. Check jetstream account limits with the JS API and delete unused streams to make room, then retry the stream creation/update
  3. If the stream is intentionally gone, remove/replace the stale assignment via stream update or API so the meta layer stops carrying the error
  4. Review server logs around the original proposal failure to identify the failing peer and its storage state
Defensive patterns

Strategy: try-catch

Validate before calling

si, err := js.StreamInfo(streamName)
if err != nil || si.Cluster == nil { /* stream materialization broken; check account limits and assignment state */ }

Type guard

func hasAssignmentErr(err error) bool {
    return strings.Contains(err.Error(), "stream assignment error")
}

Try / catch

if err := healthz(); err != nil {
    var cause error = errors.Unwrap(err) // dig past "stream assignment error: %w"
    log.Printf("stream assignment unhealthy: %v", cause)
}

Prevention

When it happens

Trigger: A stream create/update proposal failed on this peer (account limits exceeded, storage unavailable) and the failure was persisted in the assignment; health check runs while a peer's assignment still holds that error; leadership moved to a node that never successfully materialized the stream.

Common situations: Account exceeded MaxStreams/MaxBytes so a replica could not create the stream; partial cluster failure during stream creation leaving an error in the assignment; running health checks after a failed migration or scale-up of replicas.

Related errors


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