dgraph-io/dgraph · error

Cycle detected: %s

Error message

Cycle detected: %s

What it means

fragmentNode.expand expands a GraphQL+- fragment recursively; fn.Entered marks that the fragment is currently being expanded. If expand is re-entered for the same fragment before it exits, the query contains a fragment cycle (a fragment referencing itself directly or transitively), which would loop forever, so expansion aborts with this error naming the fragment.

Source

Thrown at dql/parser.go:256

// DebugPrint is useful for debugging.
func (gq *GraphQuery) DebugPrint(prefix string) {
	glog.Infof("%s[%x %q %q]\n", prefix, gq.UID, gq.Attr, gq.Alias)
	for _, c := range gq.Children {
		c.DebugPrint(prefix + "|->")
	}
}

func (gq *GraphQuery) isFragment() bool {
	return gq.fragment != ""
}

func (fn *fragmentNode) expand(fmap fragmentMap) error {
	if fn.Exited {
		// This fragment node has already been expanded.
		return nil
	}
	if fn.Entered {
		return errors.Errorf("Cycle detected: %s", fn.Name)
	}
	fn.Entered = true
	if err := fn.Gq.expandFragments(fmap); err != nil {
		return err
	}
	fn.Exited = true
	return nil
}

func (gq *GraphQuery) expandFragments(fmap fragmentMap) error {
	// We have to make a copy of children to preserve order and replace
	// fragment references with fragment content. The copy is newChildren.
	var newChildren []*GraphQuery
	// Expand non-fragments. Do not append to gq.Children.
	for _, child := range gq.Children {
		if child.isFragment() {
			fname := child.fragment // Name of fragment being referenced.
			fchild := fmap[fname]

View on GitHub (pinned to 759e242be6)

Solutions

  1. Break the cycle by removing one of the mutual fragment spreads and inlining fields instead
  2. Rename fragments to spot duplicates — also check that two different fragments don't share a name causing fmap collisions
  3. Flatten the recursive structure: expand the deepest fragment's fields manually into its parent
  4. Test the query in Ratel/Dgraph console which reports the cyclic fragment name (given in the error's %s)

Example fix

// before
fragment A on User { ...B }
fragment B on User { ...A }
// after
fragment A on User { name }
fragment B on User { name age }
Defensive patterns

Strategy: try-catch

Validate before calling

func checkNoFragmentCycle(frags map[string][]string) error {
  var visit func(name string, seen map[string]bool) error
  visit = func(name string, seen map[string]bool) error {
    if seen[name] { return fmt.Errorf("cycle at fragment %s", name) }
    seen[name] = true
    for _, dep := range frags[name] {
      if err := visit(dep, seen); err != nil { return err }
    }
    delete(seen, name)
    return nil
  }
  for name := range frags {
    if err := visit(name, map[string]bool{}); err != nil { return err }
  }
  return nil
}

Try / catch

err := dql.ParseWithNeedVars(query, vars)
var xerr *errors.Error
if errors.As(err, &xerr) && strings.Contains(xerr.Error(), "Cycle detected") {
  // fragment name is embedded after 'Cycle detected: '
  log.Fatalf("mutually recursive fragment in query: %v", xerr)
}

Prevention

When it happens

Trigger: Running dql parsing (via ParseWithNeedVars -> expandFragments -> expand) on a query where fragment A spreads fragment B and B spreads A (or A spreads itself), e.g. 'fragment A on X { ...B } fragment B on X { ...A }'.

Common situations: Copy-pasting/merging fragment definitions in large queries; refactoring queries by splitting fragments and accidentally creating mutual references; generated queries from ORMs/tools with recursive templates.

Related errors


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/a4b89f95f824c2a9. Report an issue: GitHub.