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
- Break the cycle by removing one of the mutual fragment spreads and inlining fields instead
- Rename fragments to spot duplicates — also check that two different fragments don't share a name causing fmap collisions
- Flatten the recursive structure: expand the deepest fragment's fields manually into its parent
- 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
- Keep fragment dependency graphs shallow and acyclic by design
- Lint queries for recursive spreads in CI
- Avoid generating fragments with templates that can self-reference
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
- Missing fragment: %s
- error querying persisted queries
- Invalid Math expression
- Invalid math statement. Expected 1 operands
- Division by zero
AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01).
Data as JSON: /api/errors/a4b89f95f824c2a9.
Report an issue: GitHub.