caddyserver/caddy · error

cannot add operation: maximum %d operations allowed

Error message

cannot add operation: maximum %d operations allowed

What it means

MultiRegexpFilter.AddOperation returns this when the filter already holds maxRegexpOperations (50) entries and another is added. AddOperation is used when merging multiple RegexpFilter instances into one MultiRegexpFilter, so this protects the same 50-op ceiling during merge as Validate does at config time.

Source

Thrown at modules/logging/filters.go:1000

	for _, op := range f.Operations {
		// Each regexp operation is applied sequentially
		// Using RE2 engine which is safe from ReDoS attacks
		result = op.regexp.ReplaceAllString(result, op.Value)

		// Ensure result doesn't exceed max length after each operation
		if len(result) > maxInputLength {
			result = result[:maxInputLength]
		}
	}
	return result
}

// AddOperation adds a single regexp operation to the filter with validation.
// This is used when merging multiple RegexpFilter instances.
func (f *MultiRegexpFilter) AddOperation(rawRegexp, value string) error {
	// Security checks
	if len(f.Operations) >= maxRegexpOperations {
		return fmt.Errorf("cannot add operation: maximum %d operations allowed", maxRegexpOperations)
	}

	if rawRegexp == "" {
		return fmt.Errorf("regexp pattern cannot be empty")
	}

	if len(rawRegexp) > maxPatternLength {
		return fmt.Errorf("regexp pattern too long: %d characters (maximum %d)", len(rawRegexp), maxPatternLength)
	}

	f.Operations = append(f.Operations, regexpFilterOperation{
		RawRegexp: rawRegexp,
		Value:     value,
	})
	return nil
}

// RenameFilter is a Caddy log field filter that

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Reduce the number of regexp filters being merged (drop or combine entries)
  2. Check len(f.Operations) < 50 before each AddOperation call in your own code and stop or split gracefully
  3. Restructure to explicit multi_regexp blocks you control, staying under 50

Example fix

// before
for _, op := range ops {
    _ = mf.AddOperation(op.search, op.replace) // fails silently past 50
}

// after
for _, op := range ops {
    if err := mf.AddOperation(op.search, op.replace); err != nil {
        log.Fatal(err) // or split into a new MultiRegexpFilter
    }
}
Defensive patterns

Strategy: validation

Validate before calling

if len(mf.Operations) >= 50 {
    return fmt.Errorf("cannot add operation %d: split the filter", len(mf.Operations))
}
if err := mf.AddOperation(search, replace); err != nil { return err }

Try / catch

if err := mf.AddOperation(p, v); err != nil {
    if strings.Contains(err.Error(), "maximum") { /* split into a new filter */ }
    return err
}

Prevention

When it happens

Trigger: Calling AddOperation on a MultiRegexpFilter that already has 50 operations, e.g. when Caddy (or plugin code) merges several single RegexpFilter configs into a multi filter and the combined count exceeds 50.

Common situations: A config lists many individual 'filter': 'regexp' entries that the logging pipeline coalesces into a MultiRegexpFilter; plugin authors building filters programmatically in a loop without checking the cap.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/8b25b1716c8727e0. Report an issue: GitHub.