mikefarah/yq · error

alias cycle detected

Error message

alias cycle detected

What it means

yq resolves YAML anchors and aliases by following the alias node's Alias pointer to the real node. If following that chain returns to an alias node already visited, resolution would loop forever, so yq aborts with 'alias cycle detected'. This guards against self-referential or mutually recursive anchors in the input document.

Source

Thrown at pkg/yqlib/operator_traverse_path.go:48

		if err != nil {
			return Context{}, err
		}
		matches.PushBackList(newNodes)
	}

	return context.ChildContext(matches), nil
}

// resolveAliasChain follows an alias chain iteratively, returning the
// first non-alias node. Returns an error if a cycle is detected.
func resolveAliasChain(node *CandidateNode) (*CandidateNode, error) {
	if node.Kind != AliasNode {
		return node, nil
	}
	visited := map[*CandidateNode]bool{}
	for node.Kind == AliasNode {
		if visited[node] {
			return nil, fmt.Errorf("alias cycle detected")
		}
		visited[node] = true
		log.Debug("its an alias!")
		node = node.Alias
	}
	return node, nil
}

func traverse(context Context, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
	log.Debugf("Traversing %v", NodeToString(matchingNode))

	var err error
	matchingNode, err = resolveAliasChain(matchingNode)
	if err != nil {
		return nil, err
	}

	if matchingNode.Tag == "!!null" && operation.Value != "[]" && !context.DontAutoCreate {

View on GitHub (pinned to 8b5af0694b)

Solutions

  1. Inspect the input YAML and break the alias cycle so each alias points to a non-cyclic anchor
  2. Replace the recursive alias with an explicit duplicated value or use x-expand-anchors style expansion before processing
  3. Check that your YAML serializer (e.g. go-yaml custom marshalling) is not re-using nodes recursively
  4. Validate the document with a YAML linter that detects cyclic anchors before running yq

Example fix

# before
a: &x
  b: *x
# after
a: &x
  b: 1
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check YAML for cyclic aliases before processing
func hasCyclicAliases(root *yaml.Node) bool {
	visited := map[*yaml.Node]bool{}
	var walk func(n *yaml.Node) bool
	walk = func(n *yaml.Node) bool {
		if n == nil || visited[n] { return n != nil && visited[n] }
		visited[n] = true
		if n.Kind == yaml.AliasNode { return visited[n.Alias] }
		for _, c := range n.Content { if walk(c) { return true } }
		return false
	}
	return walk(root)
}

Type guard

func isAliasNode(n *yaml.Node) bool { return n != nil && n.Kind == yaml.AliasNode }

Try / catch

out, err := yqEval(expr, doc)
if err != nil {
	if strings.Contains(err.Error(), "alias cycle detected") {
		// fall back to raw string processing or reject input
	}
	return err
}

Prevention

When it happens

Trigger: Traversing a path expression (e.g. `yq '.a.b' file.yaml`) against a document whose aliases form a cycle: an anchor whose value aliases itself, or two anchors that alias each other.

Common situations: Hand-edited or generated YAML with recursive anchors (e.g. copy-pasted Kubernetes/Compose anchors), templating tools that emit cyclic references, or programmatically built YAML via go-yaml where nodes were wired back onto themselves.

Related errors


AI-assisted analysis of mikefarah/yq@8b5af0694b (2026-09-05). Data as JSON: /api/errors/26a12c4191895f9b. Report an issue: GitHub.