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

  1. Break the cycle: build each Chain from leaf extractors only, never from itself.
  2. If a custom extractor must delegate, pass the specific inner extractor rather than the enclosing Chain.
  3. 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

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)