nats-io/nats-server · error

error adding stream: %w

Error message

error adding stream: %w

What it means

After limits checks pass, the server creates the stream in 'restore mode' via addStreamForRestore. If stream creation fails (name conflict, config rejected at store level, storage allocation failure), RestoreStreamV2 wraps the cause. Note: an exact stream-name conflict is detected earlier with JSStreamNameExistRestoreFailedError, so this wrapper usually reflects lower-level store/creation errors.

Source

Thrown at server/stream_backup.go:372

		}
		return nil
	}

	reserveCfg := cfg
	reserveCfg.MaxBytes = max(reserveCfg.MaxBytes, restoreRemaining)
	js.mu.RLock()
	err = js.checkAllLimits(&selected, tier, &reserveCfg, reserved, 0)
	js.mu.RUnlock()
	if err != nil {
		return nil, err
	}
	if err := checkUsageLimits(); err != nil {
		return nil, err
	}

	mset, err := a.addStreamForRestore(&cfg)
	if err != nil {
		return nil, fmt.Errorf("error adding stream: %w", err)
	}
	defer func() {
		var state StreamState
		mset.store.FastState(&state)
		mset.mu.Lock()
		mset.lseq = state.LastSeq
		mset.mu.Unlock()
		if err := mset.completeRestore(); err != nil {
			if err = fmt.Errorf("failed to activate stream %q: %w", cfg.Name, err); retErr == nil {
				retErr = err
			}
			s.Warnf("JetStream stream restore for '%s > %s' failed to activate stream: %v", a.Name, cfg.Name, err)
		}
	}()

	// Start off at the right sequence number. This is important in particular
	// when the backup contains no messages or would restore to no interest.
	if _, err = mset.store.Compact(nstate.FirstSeq); err != nil {

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Read the wrapped cause: if it says stream name already exists, delete the existing stream or restore under a different name
  2. Check the JetStream store directory permissions and free disk/memory for the stream's storage type
  3. Ensure no other restore/create for the same stream name is running; retry after the conflicting operation finishes
  4. Re-run `nats stream info` / check account limits, then retry the restore once resources and naming conflicts are resolved

Example fix

// before: restore without checking target
acc.RestoreStreamV2(cfg, r)
// after: ensure the stream does not already exist and the account has JetStream
if _, err := acc.LookupStream(cfg.Name); err == nil {
    return fmt.Errorf("stream %q already exists; delete it first", cfg.Name)
}
acc.RestoreStreamV2(cfg, r)
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := acc.LookupStream(cfg.Name); err == nil {
    return fmt.Errorf("stream %q exists; delete before restore", cfg.Name)
}
if err := acc.CheckJetStream(); err != nil {
    return err
}

Try / catch

mset, err := acc.RestoreStreamV2(cfg, r)
if err != nil {
    var nap ErrAPIResponse
    if errors.As(err, &nap) {
        // inspect wrapped API error (e.g. stream exists / limits) and clean up
    }
    return fmt.Errorf("restore failed adding stream: %w", err)
}

Prevention

When it happens

Trigger: addStreamForRestore fails because the store cannot create the stream: storage engine error (file perms, disk full at FS level), invalid config that slipped past earlier checks (e.g. duplicate subjects handled differently per storage backend), account/stream limits raced with another restore, or the stream was created between the earlier lookupStream check and this call.

Common situations: Two operators restoring the same stream concurrently; a stream with the same name created while the restore request was in flight; disk permissions or full filesystem on the JetStream store directory; restoring into an account whose tier limits were just tightened.

Related errors


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