nats-io/nats-server · error
import forms a cycle
Error message
import forms a cycle
What it means
ErrImportFormsCycle is returned when configuring imports would create a cycle: an import chain leads back to an already-visited account, meaning messages could route in a loop. During import validation the server walks the import graph across accounts (server/accounts.go:1716 and :1776) and checks a `visited` set; if the next account in the chain was already seen, it declares a cycle. It is also exercised by cycle-detection tests like TestAccountCycleService.
Source
Thrown at server/errors.go:150
ErrAccountResolverUpdateTooSoon = errors.New("account resolver update too soon")
// ErrAccountResolverSameClaims is returned when same claims have been fetched.
ErrAccountResolverSameClaims = errors.New("account resolver no new claims")
// ErrStreamImportAuthorization is returned when a stream import is not authorized.
ErrStreamImportAuthorization = errors.New("stream import not authorized")
// ErrStreamImportBadPrefix is returned when a stream import prefix contains wildcards.
ErrStreamImportBadPrefix = errors.New("stream import prefix can not contain wildcard tokens")
// ErrStreamImportDuplicate is returned when a stream import is a duplicate of one that already exists.
ErrStreamImportDuplicate = errors.New("stream import already exists")
// ErrServiceImportAuthorization is returned when a service import is not authorized.
ErrServiceImportAuthorization = errors.New("service import not authorized")
// ErrImportFormsCycle is returned when an import would form a cycle.
ErrImportFormsCycle = errors.New("import forms a cycle")
// ErrCycleSearchDepth is returned when we have exceeded our maximum search depth..
ErrCycleSearchDepth = errors.New("search cycle depth exhausted")
// ErrClientOrRouteConnectedToGatewayPort represents an error condition when
// a client or route attempted to connect to the Gateway port.
ErrClientOrRouteConnectedToGatewayPort = errors.New("attempted to connect to gateway port")
// ErrWrongGateway represents an error condition when a server receives a connect
// request from a remote Gateway with a destination name that does not match the server's
// Gateway's name.
ErrWrongGateway = errors.New("wrong gateway")
// ErrGatewayNameHasSpaces signals that the gateway name contains spaces, which is not allowed.
ErrGatewayNameHasSpaces = errors.New("gateway name cannot contain spaces")
// ErrNoSysAccount is returned when an attempt to publish or subscribe is made
// when there is no internal system account defined.View on GitHub (pinned to 3a66a489d2)
Solutions
- Break the loop: remove or redirect the import (or mapping) that points back to an account already in the chain.
- Redesign the chain so exports/imports form a directed acyclic graph, e.g. have B consume A but not re-export A's subject back.
- Draw the account import/export graph (or script it from the account JWTs) before adding new imports.
- If a relay is genuinely needed, use a distinct subject namespace on the middle account so the original subject is not re-exported.
Example fix
// before (cycle: A imports B, B imports A's same subject) // A: import svc.* from B ; B: import svc.* from A // after (acyclic) // A: import svc.* from B ; B: no import of A's svc.* namespace
Defensive patterns
Strategy: validation
Validate before calling
// Validate the account import graph is acyclic before applying configs: // parse each account's imports/exports, build edges acc(from)->acc(to), and run a DFS // keeping a visited set; reject the change if any back-edge is found.
Try / catch
if err := applyAccountConfig(); err != nil {
if errors.Is(err, ErrImportFormsCycle) {
// log the import chain and roll back the config change
}
} Prevention
- Model account imports as a DAG in CI and run cycle detection on every config PR.
- Never re-export a subject namespace you imported from another account under the same subject.
- Use distinct subject prefixes per account tier (e.g. tierA.svc.*, tierB.svc.*).
- Review mapping rules that could re-enter an origin account.
When it happens
Trigger: Declaring an import on account A that (directly or transitively through B, C, ...) points back to A; two accounts mutually importing each other's exports; a longer chain closed by a mapping (`subject_mapping`) that re-enters a visited account.
Common situations: Dev/test environments where accounts mirror each other's exports for convenience; incremental config edits that accidentally close a loop; renaming/renaming-mapping imports creating self-references.
Related errors
- service import not authorized
- stream import not authorized
- stream import prefix can not contain wildcard tokens
- account jwt not found
- auth callout violation: auth callout response is not for exp
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/18a2bb6a43a82a92.
Report an issue: GitHub.