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
- Inspect the wrapped cause (errors.Unwrap / %w chain) to find the underlying failure (e.g. account limits) and remediate it (raise limits, free storage)
- Check jetstream account limits with the JS API and delete unused streams to make room, then retry the stream creation/update
- If the stream is intentionally gone, remove/replace the stale assignment via stream update or API so the meta layer stops carrying the error
- 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
- Monitor account limits (MaxStreams, MaxBytes) before creating streams
- Check jsz/monitoring output for assignment errors after cluster changes
- Remediate the wrapped root cause rather than ignoring health-check failures
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
- stream missing
- stream assignment or group missing
- stream not found
- group node missing
- cluster node skew detected
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/a1e43bc84d2a3470.
Report an issue: GitHub.