JuliusBrussee/caveman · error · ErrInvalidRule

%w: rule %q: %s

Error message

%w: rule %q: %s

What it means

regexp.Compile failed for the rule's Pattern; the error wraps ErrInvalidRule and includes the rule's Name plus the underlying regexp/syntax error text (position and reason). The Go RE2 engine rejects several constructs common elsewhere: backreferences ((\1)), lookaround ((?=...)), and some invalid repetition/escape spellings.

Source

Thrown at shared/platform/redact/payload.go:586

			// A reference to the unconditional floor. Nothing to run, and
			// nothing it could switch off.
			continue
		case RuleTypeRegex:
		case RuleTypeJSONPath, RuleTypeHeader:
			return nil, "", fmt.Errorf("%w: %q (rule %q)", ErrRuleUnsupported, r.Type, r.Name)
		default:
			return nil, "", fmt.Errorf("%w: %q (rule %q)", ErrRuleUnsupported, r.Type, r.Name)
		}

		if strings.TrimSpace(r.Name) == "" {
			return nil, "", fmt.Errorf("%w: empty name", ErrInvalidRule)
		}
		if r.Pattern == "" {
			return nil, "", fmt.Errorf("%w: rule %q has an empty pattern", ErrInvalidRule, r.Name)
		}
		re, err := regexp.Compile(r.Pattern)
		if err != nil {
			return nil, "", fmt.Errorf("%w: rule %q: %s", ErrInvalidRule, r.Name, err)
		}
		if re.MatchString("") {
			// Such a pattern matches at every position and would replace the
			// whole body with placeholders. Refuse it rather than destroy the
			// capture.
			return nil, "", fmt.Errorf("%w: rule %q matches the empty string", ErrInvalidRule, r.Name)
		}
		repl := r.Replacement
		if repl == "" {
			repl = "[REDACTED:" + r.Name + "]"
		}
		out = append(out, compiledRule{
			name:                 r.Name,
			origin:               OriginOrg,
			re:                   re,
			replIntroducesNeedle: introducesNeedle([]byte(repl)),
			// The replacement is operator-supplied data, not a regexp
			// template: a literal replace keeps "$1" from expanding a captured

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Rewrite the pattern for RE2: replace lookbehind with a captured group (token:\s*(\w+)) and backreferences with explicit alternation or a different strategy.
  2. Test patterns with regexp.Compile in a unit test over the rule fixture so syntax errors surface in CI, not in the capture path.
  3. If the pattern came from string interpolation, use regexp.QuoteMeta for literal parts.

Example fix

// before
{Name: "after-token", Type: redact.RuleTypeRegex, Pattern: `(?<=token:)[A-Za-z0-9]+`} // lookbehind unsupported

// after
{Name: "after-token", Type: redact.RuleTypeRegex, Pattern: `token:\s*([A-Za-z0-9]+)`} // capture group, replacement keeps $-free literal
Defensive patterns

Strategy: validation

Validate before calling

func compileAllRules(rules []redact.Rule) error {
    for _, r := range rules {
        if r.Type != redact.RuleTypeRegex { continue }
        if _, err := regexp.Compile(r.Pattern); err != nil {
            return fmt.Errorf("rule %q: %w", r.Name, err)
        }
    }
    return nil
}

Type guard

func compiles(p string) bool { _, err := regexp.Compile(p); return err == nil }

Try / catch

if _, _, err := redact.Payload(body, rules); err != nil {
    if errors.Is(err, redact.ErrInvalidRule) && strings.Contains(err.Error(), ": error parsing") {
        // rewrite the pattern for RE2 (no lookaround/backreferences) and redeploy rules
    }
}

Prevention

When it happens

Trigger: A rule authored with PCRE syntax — e.g. Pattern: `"(?<=token:)\s+\w+"` or `(a+)\1` — passed to redact.Payload; the compile step runs before any body is processed, so the error fires on the first call regardless of body content.

Common situations: Porting redaction rules written for PCRE/Perl/ripgrep-style engines; regex validated in a JS UI (different dialect) and deployed to the Go backend; unescaped user input interpolated into a pattern (a stray '(' breaks it).

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/ac4a7fd84bd4a87f. Report an issue: GitHub.