gorilla/mux · critical

route %s contains capture groups in its regexp. Only non-cap

Error message

route %s contains capture groups in its regexp. Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)

What it means

This is a PANIC (regexp.go:138-141), not a returned error. After compiling the full route regexp, the library checks reg.NumSubexp() equals the number of declared variables; if not, it panics because the user's pattern introduced extra capturing groups (e.g. (a|b)), which mux forbids. Only named groups for variables and non-capturing groups (?:...) are allowed. The panic occurs during route construction (Path/Host/PathPrefix/Queries), crashing the program at startup unless recovered.

Source

Thrown at regexp.go:139

		// Add the default pattern if the query value is empty
		if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
			pattern.WriteString(defaultPattern)
		}
	}
	if typ != regexpTypePrefix {
		pattern.WriteByte('$')
	}

	// Compile full regexp.
	patternStr := pattern.String()
	reg, errCompile := RegexpCompileFunc(patternStr)
	if errCompile != nil {
		return nil, errCompile
	}

	// Check for capturing groups which used to work in older versions
	if reg.NumSubexp() != len(idxs)/2 {
		panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
			"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
	}

	var wildcardHostPort bool
	if typ == regexpTypeHost {
		if !strings.Contains(patternStr, ":") {
			wildcardHostPort = true
		}
	}
	reverse.WriteString(raw)
	if endSlash {
		reverse.WriteByte('/')
	}

	// Done!
	return &routeRegexp{
		template:         template,
		regexpType:       typ,

View on GitHub (pinned to db9d1d0073)

Solutions

  1. Replace every capturing group (...) inside a variable pattern with a non-capturing group (?:...).
  2. Re-run; the panic disappears once NumSubexp matches the variable count.
  3. If patterns come from untrusted input, wrap route construction in a func with defer recover() and log the offending template.

Example fix

// before
r.Path("/{x:(a|b)}")
// panic: route /{x:(a|b)} contains capture groups...

// after
r.Path("/{x:(?:a|b)}")
Defensive patterns

Strategy: validation

Validate before calling

func noCaptureGroups(p string) error {
    re, err := regexp.Compile(p)
    if err != nil { return err }
    if re.NumSubexp() > 0 {
        return fmt.Errorf("pattern %q has %d capture groups; use (?:...)", p, re.NumSubexp())
    }
    return nil
}

Type guard

func safePattern(p string) bool {
    re, err := regexp.Compile(p)
    return err == nil && re.NumSubexp() == 0
}

Try / catch

// Go has no try/catch; recover a panic at the construction boundary
defer func() {
    if r := recover(); r != nil {
        log.Printf("route build panicked: %v", r)
    }
}()
rt := r.Path(tpl)

Prevention

When it happens

Trigger: r.Path("/{x:(a|b)}") — the inner (a|b) is a capturing group adding a subexpression beyond the named {x} group. Any bare (...) inside a variable pattern that is not (?:...).

Common situations: Porting regexes from languages whose parens capture by default; writing {lang:(en|fr|de)} instead of {lang:(?:en|fr|de)}; forgetting RE2 still creates capture groups for bare parens.

Related errors


AI-assisted analysis of gorilla/mux@db9d1d0073 (2026-08-04). Data as JSON: /data/errors/eed73b8335a6e1e0.json. Report an issue: GitHub.