nats-io/nats-server · error
variable reference cycle for '%s'
Error message
variable reference cycle for '%s'
What it means
After exhausting all context maps without finding the variable, lookupVariable detects a self-referential cycle: p.envVarReferences already contains varReference, meaning this same reference is being resolved recursively (a variable whose resolution re-triggers itself). The parser aborts rather than looping forever.
Source
Thrown at conf/parse.go:468
if strings.HasPrefix(varReference, bcryptPrefix) {
return "$" + varReference, true, nil
}
// Loop through contexts currently on the stack.
for i := len(p.ctxs) - 1; i >= 0; i-- {
ctx := p.ctxs[i]
// Process if it is a map context
if m, ok := ctx.(map[string]any); ok {
if v, ok := m[varReference]; ok {
return v, ok, nil
}
}
}
// If we are here, we have exhausted our context maps and still not found anything.
// Detect reference cycles
if p.envVarReferences[varReference] {
return nil, false, fmt.Errorf("variable reference cycle for '%s'", varReference)
}
p.envVarReferences[varReference] = true
defer delete(p.envVarReferences, varReference)
// Parse from the environment
if vStr, ok := os.LookupEnv(varReference); ok {
// Everything we get here will be a string value, so we need to process as a parser would.
if subp, err := parseEnv(fmt.Sprintf("%s=%s", pkey, vStr), p); err == nil {
v, ok := subp.mapping[pkey]
return v, ok, nil
} else {
return nil, false, err
}
}
return nil, false, nil
}
func (p *parser) setValue(val any) {View on GitHub (pinned to 3a66a489d2)
Solutions
- Break the cycle: make the variable's value literal instead of a reference to itself.
- Rename one of the mutually-referencing variables so the chain terminates at a literal.
- Set the terminal value directly in the environment or config instead of through the recursive reference.
- Trace the reference chain named in the error to find which entry loops back.
Example fix
// before
HOME_DIR = ${HOME_DIR}/bin
// after
HOME_DIR = /home/user
BIN_DIR = ${HOME_DIR}/bin Defensive patterns
Strategy: validation
Validate before calling
// Detect self-references before parsing
for key, val := range cfg {
if s, ok := val.(string); ok && strings.Contains(s, "${"+key+"}") {
return fmt.Errorf("variable %s references itself", key)
}
} Try / catch
if err := parseConfig(); err != nil {
if strings.Contains(err.Error(), "variable reference cycle") {
return fmt.Errorf("cyclic variable definition in config: %w", err)
}
return err
} Prevention
- Never define a variable whose value references itself.
- Draw the variable reference chain and ensure it terminates at a literal.
- Avoid shadowing env var names with config keys that reference the same name.
When it happens
Trigger: lookupVariable (called by processItem) is re-entered for the same varReference while p.envVarReferences[varReference] is still true — e.g. a variable value that itself references the same variable, or include chains that re-enter the same reference.
Common situations: A config key defined as a reference to itself (${PATH} = ${PATH} in config), two variables referencing each other, or an env var plus config entry pointing back at the same reference so resolution never terminates.
Related errors
- variable reference for '%s' on line %d could not be parsed:
- variable reference for '%s' on line %d can not be found
- import forms a cycle
- mqtt authentication token not compatible with presence of us
- ack wait must be a positive value
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/0b6da333a53bd175.
Report an issue: GitHub.