nektos/act · error
circular alias
Error message
circular alias
What it means
resolveAliasesExt walks the parsed YAML tree inlining alias nodes into their anchors; the path map tracks nodes currently being expanded so that an alias chain that re-enters itself is detected. When a node is encountered that is already on the expansion path, the YAML contains a cycle (an anchor that, directly or through nesting, references its own alias), and 'circular alias' is returned, aborting workflow parsing.
Source
Thrown at pkg/model/anchors.go:11
package model
import (
"errors"
"gopkg.in/yaml.v3"
)
func resolveAliasesExt(node *yaml.Node, path map[*yaml.Node]bool, skipCheck bool) error {
if !skipCheck && path[node] {
return errors.New("circular alias")
}
switch node.Kind {
case yaml.AliasNode:
aliasTarget := node.Alias
if aliasTarget == nil {
return errors.New("unresolved alias node")
}
path[node] = true
*node = *aliasTarget
if err := resolveAliasesExt(node, path, true); err != nil {
return err
}
delete(path, node)
case yaml.DocumentNode, yaml.MappingNode, yaml.SequenceNode:
for _, child := range node.Content {
if err := resolveAliasesExt(child, path, false); err != nil {
return errView on GitHub (pinned to 4f41128141)
Solutions
- Find the self-reference: search the workflow for repeated anchor/alias names and check whether the anchor's block contains '*same_name'.
- Break the cycle by duplicating the shared value or restructuring into two anchors where the second references the first only one way.
- If generated YAML, fix the generator so it does not emit recursive structures.
- Validate the file with a strict YAML linter or 'python -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))"' to confirm the cycle is gone (safe_load rejects recursive aliases by default).
Example fix
# before env: &base A: 1 B: *base # circular alias # after base: &base A: 1 B: 2 env: *base
Defensive patterns
Strategy: validation
Validate before calling
# Reject circular anchors before running act
python3 - <<'EOF'
import sys, yaml
class Loader(yaml.SafeLoader):
pass
def no_alias_constructor(loader, node, deep=False):
raise ValueError(f"circular/unsupported alias at line {node.start_mark.line}")
Loader.add_constructor(None, no_alias_constructor) # strict mode catches cycles early
try:
yaml.load(open(sys.argv[1]).read(), Loader=yaml.SafeLoader)
except yaml.constructor.ConstructorError as e:
sys.exit(f"YAML problem: {e}")
print("ok")
EOF Try / catch
planner, err := model.NewWorkflowPlanner(".github/workflows", true)
if err != nil {
if strings.Contains(err.Error(), "circular alias") {
return fmt.Errorf("workflow YAML has an anchor that references itself; break the cycle: %w", err)
}
return err
} Prevention
- Avoid anchors whose value aliases themselves, even indirectly through nested nodes.
- Run any strict YAML validator on workflow files in CI.
- When generating YAML, avoid emitting recursive data structures.
When it happens
Trigger: Hand-crafted or machine-generated YAML where an anchor's value contains an alias back to that same anchor, e.g. 'a: &x {b: *x}', or a longer mutual chain across mapping/sequence nodes. YAML serializers that emit self-referential structures produce this on re-parse.
Common situations: Matrix or env blocks refactored with YAML anchors where a copy accidentally references the original; templating tools (yq, Jinja emitting YAML) that generate self-referential merges; attempting recursive defaults like 'defaults: &d { inherit: *d }'.
Related errors
- unresolved alias node
- ErrShortRef
- The runs.using key in action.yml must be one of: %v, got %s
- unable to read workflow '%s': file is empty: %w
- workflow is not valid. '%s': %w
AI-assisted analysis of nektos/act@4f41128141 (2026-08-15).
Data as JSON: /api/errors/7c2303f0df01700f.
Report an issue: GitHub.