semaphoreui/semaphore · error

cyclic dependency detected involving

Error message

cyclic dependency detected involving %s

What it means

The export chain validates the dependency order of exporter types as a directed graph before exporting. If the dependency graph contains a cycle, visiting() detects a node that is currently on the visit stack and aborts with the name of one type involved in the cycle.

Solutions

  1. Break the cycle by removing or reordering the dependency declaration between the implicated types
  2. Restructure the custom exporter so dependencies flow one way (e.g. split into two passes)
  3. Trace dependencies starting from the type named in the message to find the loop edge

Example fix

// before: project depends on template, template depends on project
exporter.addDependency("template") // in project exporter
exporter.addDependency("project")  // in template exporter -> cycle
// after: remove the back-edge
// template no longer declares a dependency on project
Defensive patterns

Strategy: try-catch

Validate before calling

// detect cycle before running export
deps := map[string][]string{"a": {"b"}, "b": {"a"}}
var hasCycle func(n string, stack map[string]bool) bool
hasCycle = func(n string, stack map[string]bool) bool {
    if stack[n] { return true }
    stack[n] = true
    for _, d := range deps[n] { if hasCycle(d, stack) { return true } }
    stack[n] = false
    return false
}

Try / catch

if err := chain.export(); err != nil {
    if strings.Contains(err.Error(), "cyclic dependency") {
        log.Fatalf("fix exporter dependency declarations: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: Registering exporters whose ExportOrder/dependency declarations form a cycle (A depends on B, B on A — possibly through longer chains), then running the topological sort during export/import.

Common situations: Adding a new custom exporter that declares a dependency on a type which (transitively) depends back on it; refactoring dependency lists so two entity types mutually reference each other.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/14fd13a5d646c2dd. Report an issue: GitHub.

Appendix: source

Thrown at services/export/Exporter.go:363

		n, err := strconv.Atoi(v)

		if err != nil {
			return nil, err
		}
		out[i] = n
	}
	return out, nil
}

func getSortedKeys(exporters map[string]TypeExporter, dependsOn func(t TypeExporter) []string) ([]string, error) {
	var sorted []string
	visited := make(map[string]bool)
	visiting := make(map[string]bool)

	var visit func(name string) error
	visit = func(name string) error {
		if visiting[name] {
			return fmt.Errorf("cyclic dependency detected involving %s", name)
		}
		if visited[name] {
			return nil
		}

		visiting[name] = true

		if exporter, ok := exporters[name]; ok {

			order := dependsOn(exporter)

			for _, dep := range order {
				if _, exists := exporters[dep]; exists {
					if err := visit(dep); err != nil {
						return err
					}
				}
			}

View on GitHub (pinned to 1774ccb71a)