{"record":{"id":"0b6da333a53bd175","repo":"nats-io/nats-server","slug":"variable-reference-cycle-for-s","errorCode":null,"errorMessage":"variable reference cycle for '%s'","messagePattern":"variable reference cycle for '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"conf/parse.go","lineNumber":468,"sourceCode":"\tif strings.HasPrefix(varReference, bcryptPrefix) {\n\t\treturn \"$\" + varReference, true, nil\n\t}\n\n\t// Loop through contexts currently on the stack.\n\tfor i := len(p.ctxs) - 1; i >= 0; i-- {\n\t\tctx := p.ctxs[i]\n\t\t// Process if it is a map context\n\t\tif m, ok := ctx.(map[string]any); ok {\n\t\t\tif v, ok := m[varReference]; ok {\n\t\t\t\treturn v, ok, nil\n\t\t\t}\n\t\t}\n\t}\n\n\t// If we are here, we have exhausted our context maps and still not found anything.\n\t// Detect reference cycles\n\tif p.envVarReferences[varReference] {\n\t\treturn nil, false, fmt.Errorf(\"variable reference cycle for '%s'\", varReference)\n\t}\n\tp.envVarReferences[varReference] = true\n\tdefer delete(p.envVarReferences, varReference)\n\n\t// Parse from the environment\n\tif vStr, ok := os.LookupEnv(varReference); ok {\n\t\t// Everything we get here will be a string value, so we need to process as a parser would.\n\t\tif subp, err := parseEnv(fmt.Sprintf(\"%s=%s\", pkey, vStr), p); err == nil {\n\t\t\tv, ok := subp.mapping[pkey]\n\t\t\treturn v, ok, nil\n\t\t} else {\n\t\t\treturn nil, false, err\n\t\t}\n\t}\n\treturn nil, false, nil\n}\n\nfunc (p *parser) setValue(val any) {","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/conf/parse.go#L450-L486","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nHOME_DIR = ${HOME_DIR}/bin\n// after\nHOME_DIR = /home/user\nBIN_DIR = ${HOME_DIR}/bin","handlingStrategy":"validation","validationCode":"// Detect self-references before parsing\nfor key, val := range cfg {\n\tif s, ok := val.(string); ok && strings.Contains(s, \"${\"+key+\"}\") {\n\t\treturn fmt.Errorf(\"variable %s references itself\", key)\n\t}\n}","typeGuard":null,"tryCatchPattern":"if err := parseConfig(); err != nil {\n\tif strings.Contains(err.Error(), \"variable reference cycle\") {\n\t\treturn fmt.Errorf(\"cyclic variable definition in config: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["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."],"tags":["config","variables","cycle","recursion"],"backgroundTag":"variable-reference-cycle","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}