golang/go · error · PackageError
import cycle not allowed
Error message
import cycle not allowed
What it means
Go forbids circular imports between packages. reusePackage detects a package that is in the middle of its own loadPackage recursion (its Internal.Imports slice is still nil because it is only set after the recursion completes), which can only happen if the import stack has looped back to a package currently being loaded. The package is marked Incomplete and the recorded ImportStack shows the cycle path.
Source
Thrown at src/cmd/go/internal/load/pkg.go:1446
if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
return true
}
}
return false
}
// reusePackage reuses package p to satisfy the import at the top
// of the import stack stk. If this use causes an import loop,
// reusePackage updates p's error information to record the loop.
func reusePackage(p *Package, stk *ImportStack) *Package {
// We use p.Internal.Imports==nil to detect a package that
// is in the midst of its own loadPackage call
// (all the recursion below happens before p.Internal.Imports gets set).
if p.Internal.Imports == nil {
if p.Error == nil {
p.Error = &PackageError{
ImportStack: stk.Copy(),
Err: errors.New("import cycle not allowed"),
IsImportCycle: true,
}
} else if !p.Error.IsImportCycle {
// If the error is already set, but it does not indicate that
// we are in an import cycle, set IsImportCycle so that we don't
// end up stuck in a loop down the road.
p.Error.IsImportCycle = true
}
p.Incomplete = true
}
// Don't rewrite the import stack in the error if we have an import cycle.
// If we do, we'll lose the path that describes the cycle.
if p.Error != nil && p.Error.ImportStack != nil &&
!p.Error.IsImportCycle && stk.shorterThan(p.Error.ImportStack.Pkgs()) {
p.Error.ImportStack = stk.Copy()
}
return p
}View on GitHub (pinned to b6b368adc5)
Solutions
- Break the cycle by extracting the shared code into a third package that both packages import.
- Invert the dependency: move the depending code into the package it currently depends on.
- Decouple at compile time with an interface defined in a lower-level package and implemented where needed.
- Run `go list -deps` or `go vet` to confirm the cycle is gone after the refactor.
Example fix
// before: pkg a imports b, pkg b imports a -> cycle // after: move shared types into a/new shared package c // a -> c, b -> c (no edge between a and b)
Defensive patterns
Strategy: validation
Validate before calling
// Run before pushing: `go build ./...` fails fast on cycles.
// Programmatically, walk the import graph and assert acyclicity:
// out, err := exec.Command("go", "list", "-deps", "./...").Output()
// then check `go list -deps -f '{{.ImportPath}} {{.Imports}}' ./...` for back-edges. Prevention
- Keep packages small and focused on one responsibility to reduce cross-package coupling.
- Avoid bidirectional dependencies; if A and B reference each other, extract a lower package C.
- Run `go build ./...` or `go vet` frequently so cycles surface at the introducing commit.
When it happens
Trigger: Package A imports B and B imports A (directly or transitively); during the depth-first load, reusePackage is invoked on package A while A.Internal.Imports is still nil because A's own loadPackage call has not yet returned.
Common situations: Splitting a package into two that both need each other's symbols; adding an import that closes a loop; refactoring that moves shared types so a dependency points back to the originator; test helper package imported by production code that itself imports the test target.
Related errors
- import cycle not allowed in test
- multiple //go:build comments
- syntax error
- use of vendored package not allowed
- binary-only packages are no longer supported
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/899215d44fc2d6d0.
Report an issue: GitHub.