{"record":{"id":"18a2bb6a43a82a92","repo":"nats-io/nats-server","slug":"import-forms-a-cycle","errorCode":null,"errorMessage":"import forms a cycle","messagePattern":"import forms a cycle","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/errors.go","lineNumber":150,"sourceCode":"\tErrAccountResolverUpdateTooSoon = errors.New(\"account resolver update too soon\")\n\n\t// ErrAccountResolverSameClaims is returned when same claims have been fetched.\n\tErrAccountResolverSameClaims = errors.New(\"account resolver no new claims\")\n\n\t// ErrStreamImportAuthorization is returned when a stream import is not authorized.\n\tErrStreamImportAuthorization = errors.New(\"stream import not authorized\")\n\n\t// ErrStreamImportBadPrefix is returned when a stream import prefix contains wildcards.\n\tErrStreamImportBadPrefix = errors.New(\"stream import prefix can not contain wildcard tokens\")\n\n\t// ErrStreamImportDuplicate is returned when a stream import is a duplicate of one that already exists.\n\tErrStreamImportDuplicate = errors.New(\"stream import already exists\")\n\n\t// ErrServiceImportAuthorization is returned when a service import is not authorized.\n\tErrServiceImportAuthorization = errors.New(\"service import not authorized\")\n\n\t// ErrImportFormsCycle is returned when an import would form a cycle.\n\tErrImportFormsCycle = errors.New(\"import forms a cycle\")\n\n\t// ErrCycleSearchDepth is returned when we have exceeded our maximum search depth..\n\tErrCycleSearchDepth = errors.New(\"search cycle depth exhausted\")\n\n\t// ErrClientOrRouteConnectedToGatewayPort represents an error condition when\n\t// a client or route attempted to connect to the Gateway port.\n\tErrClientOrRouteConnectedToGatewayPort = errors.New(\"attempted to connect to gateway port\")\n\n\t// ErrWrongGateway represents an error condition when a server receives a connect\n\t// request from a remote Gateway with a destination name that does not match the server's\n\t// Gateway's name.\n\tErrWrongGateway = errors.New(\"wrong gateway\")\n\n\t// ErrGatewayNameHasSpaces signals that the gateway name contains spaces, which is not allowed.\n\tErrGatewayNameHasSpaces = errors.New(\"gateway name cannot contain spaces\")\n\n\t// ErrNoSysAccount is returned when an attempt to publish or subscribe is made\n\t// when there is no internal system account defined.","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/errors.go#L132-L168","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (cycle: A imports B, B imports A's same subject)\n// A: import svc.* from B ; B: import svc.* from A\n// after (acyclic)\n// A: import svc.* from B ; B: no import of A's svc.* namespace","handlingStrategy":"validation","validationCode":"// Validate the account import graph is acyclic before applying configs:\n// parse each account's imports/exports, build edges acc(from)->acc(to), and run a DFS\n// keeping a visited set; reject the change if any back-edge is found.","typeGuard":null,"tryCatchPattern":"if err := applyAccountConfig(); err != nil {\n    if errors.Is(err, ErrImportFormsCycle) {\n        // log the import chain and roll back the config change\n    }\n}","preventionTips":["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."],"tags":["nats","account-jwt","cycle","service-import","stream-import"],"backgroundTag":"import-cycle-detected","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}