gofiber/fiber · error
cyclic extractor chain
Error message
cyclic extractor chain
What it means
ErrChainCycle is returned by the Chain extractor (extractors/extractors.go:537) when it detects that the same chain has been re-entered recursively within a single request. Each Chain call mints a unique guard key stored in c.Locals; on re-entry the guard is already true and the Extract closure short-circuits with the sentinel instead of looping forever.
Solutions
- Break the cycle: build each Chain from leaf extractors only, never from itself.
- If a custom extractor must delegate, pass the specific inner extractor rather than the enclosing Chain.
- Add a unit test that builds the chain and asserts Extract does not return ErrChainCycle on a representative request.
Example fix
// before chain := extractors.Chain(extA, extB) // later, accidentally: chain = extractors.Chain(chain, extC) // re-entry // after chain := extractors.Chain(extA, extB, extC)
Defensive patterns
Strategy: validation
Validate before calling
// Assert at construction that no Chain references itself, directly or
// transitively. A simple identity check catches the common direct case.
func buildChain(leaf extractors.Extractor, rest ...extractors.Extractor) extractors.Extractor {
for _, e := range rest {
if sameExtractor(e, leaf) {
panic("chain cycle: leaf re-entered")
}
}
return extractors.Chain(append([]extractors.Extractor{leaf}, rest...)...)
} Type guard
// isChainCycle reports whether a Chain detected recursive re-entry.
func isChainCycle(err error) bool {
return errors.Is(err, extractors.ErrChainCycle)
} Try / catch
val, err := chain.Extract(c)
if err != nil {
if isChainCycle(err) {
log.Printf("extractor chain cycle; misconfigured auth pipeline")
return c.SendStatus(fiber.StatusInternalServerError)
}
return err
} Prevention
- Build each Chain from leaf extractors only; never include the Chain itself.
- Custom extractors that delegate should reference a specific inner extractor, not the enclosing Chain.
- Add a unit test asserting Extract does not return ErrChainCycle on a representative request.
When it happens
Trigger: Building a Chain whose inner list contains the Chain itself (directly or transitively), or a custom extractor (FromCustom) that calls Extract on its enclosing chain. On the first re-entry within the same request, the Locals guard at extractors.go:534-541 fires.
Common situations: Constructing a fallback chain where the tail accidentally references the head; a custom extractor that calls a parent extractor to delegate; refactoring a linear chain into a recursive structure without breaking the cycle.
Related errors
AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11).
Data as JSON: /api/errors/65492ed85b27d265.
Report an issue: GitHub.
Appendix: source
Thrown at extractors/extractors.go:68
// SourceQuery indicates the value is extracted from URL query parameters.
SourceQuery
// SourceParam indicates the value is extracted from URL path parameters.
SourceParam
// SourceCookie indicates the value is extracted from cookies.
SourceCookie
// SourceCustom indicates the value is extracted using a custom extractor function.
SourceCustom
)
// ErrNotFound is returned when the requested value is missing or empty.
var ErrNotFound = errors.New("value not found")
// ErrChainCycle is returned when a chain extractor recursively invokes itself.
var ErrChainCycle = errors.New("cyclic extractor chain")
// Extractor defines a value extraction method with metadata.
type Extractor struct {
Extract func(fiber.Ctx) (string, error)
Key string // The parameter/header name used for extraction
AuthScheme string // The auth scheme used, e.g., "Bearer"
Chain []Extractor // For chained extractors, stores all extractors in the chain
Source Source // The type of source being extracted from
}
// Contains reports whether this extractor, or any extractor in its chain, matches pred.
//
// If pred is nil, Contains returns false.
func (e Extractor) Contains(pred func(Extractor) bool) bool {
if pred == nil {
return false
}
View on GitHub (pinned to a105acad6c)