charmbracelet/crush · error

hook %s[%d]: invalid matcher regex %q: %w

Error message

hook %s[%d]: invalid matcher regex %q: %w

What it means

ValidateHooks checks every hook in the config, requiring a non-empty command and a syntactically valid matcher regex. This error is returned when a hook's matcher string is non-empty but regexp.Compile rejects it, wrapping the underlying regexp parse error (unclosed group, bad escape, etc.). Crush validates hooks at load and reload time so a bad matcher never silently matches all tools or crashes the hook runner.

Source

Thrown at internal/config/load.go:1452

	// Normalize event name keys.
	for event, eventHooks := range c.Hooks {
		canonical := normalizeHookEvent(event)
		if canonical != event {
			c.Hooks[canonical] = append(c.Hooks[canonical], eventHooks...)
			delete(c.Hooks, event)
		}
	}

	for event, eventHooks := range c.Hooks {
		for i, h := range eventHooks {
			if h.Command == "" {
				return fmt.Errorf("hook %s[%d]: command is required", event, i)
			}
			if h.Matcher == "" {
				continue
			}
			if _, err := regexp.Compile(h.Matcher); err != nil {
				return fmt.Errorf("hook %s[%d]: invalid matcher regex %q: %w", event, i, h.Matcher, err)
			}
		}
	}
	return nil
}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Test the pattern with `go run` / regex101 in Go mode and fix the syntax error reported after the colon in the wrapped message.
  2. Remove the matcher field entirely (empty string means match all tools and is allowed).
  3. Escape regex metacharacters: use `\\(` for a literal paren, `\\.` for a literal dot.
  4. Re-run `crush` or reload config to confirm ValidateHooks passes.

Example fix

// before (crush.json)
{"hooks": {"PreToolUse": [{"matcher": "Bash(git *)", "command": "lint.sh"}]}}
// after
{"hooks": {"PreToolUse": [{"matcher": "^Bash$", "command": "lint.sh"}]}}
Defensive patterns

Strategy: validation

Validate before calling

for event, hooks := range cfg.Hooks {
    for i, h := range hooks {
        if h.Matcher == "" { continue }
        if _, err := regexp.Compile(h.Matcher); err != nil {
            return fmt.Errorf("hook %s[%d]: invalid matcher regex %q: %w", event, i, h.Matcher, err)
        }
    }
}

Try / catch

if err := cfg.ValidateHooks(); err != nil {
    var re *regexp.SyntaxError
    if errors.As(err, &re) {
        slog.Error("fix hook matcher regex", "detail", re.String())
    }
    return err
}

Prevention

When it happens

Trigger: Calling config.Load, ReloadFromDisk (reloadFromDiskLocked), ValidateHooks directly, or newRunner with a config whose HookConfig.Matcher is a non-empty invalid regex, e.g. "bash(", "^bash$[", or "tool\(".

Common situations: Hand-editing crushrc or crush.json hooks and typosing the pattern; copying Claude Code hook matchers that use glob-ish syntax ("Bash(* git *)") not valid Go regexp; escaping mistakes when writing backslashes in JSON.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/2d31f4d995b33323. Report an issue: GitHub.