charmbracelet/crush · error
invalid hook configuration on reload
Error message
invalid hook configuration on reload: %w
What it means
On reload, hooks defined in the reloaded config must be re-validated (matcher regexes recompiled) via cfg.ValidateHooks, mirroring initial Load. If validation fails, reload aborts with this wrapped error and the store rolls back, keeping the previously working hooks.
Solutions
- Fix the hook definition flagged by the wrapped error — usually an invalid matcher regex or missing command field.
- Test the regex in isolation (go playground or regex101 with RE2 syntax) before adding it to config.
- Roll back the recent hook edit and reload to restore the working state.
- Validate hooks locally via the config validation path before sharing the config.
Example fix
// before: invalid regex hook pretooluse --matcher "(edit|write" --command ./check.sh // after hook pretooluse --matcher "(edit|write)" --command ./check.sh
Defensive patterns
Strategy: validation
Validate before calling
for _, h := range cfg.Hooks { if _, err := regexp.Compile(h.Matcher); err != nil { return err }; if h.Command == "" { return errors.New("empty hook command") } } Try / catch
if err := reload(); err != nil && strings.Contains(err.Error(), "invalid hook configuration") {
revertLastHookEdit(); reload()
} Prevention
- Test hook matcher regexes (RE2) before committing config
- Keep hook commands non-empty and executable
- Validate hooks in CI with the same config schema
When it happens
Trigger: cfg.ValidateHooks() returns an error after merging: a hook with an invalid matcher regex, empty/missing command, or unsupported event name in the newly loaded crushrc/crush.json.
Common situations: User added a hook in the session directory with a bad regex like "(" or a typo'd event; a shared team config was updated with an incompatible hook schema version.
Related errors
- hook [ ]: invalid matcher regex
- invalid hook configuration
- hook [ ]: command is required
- invalid regex pattern
- agent message id missing from context
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/5a0de138306d390b.
Report an issue: GitHub.
Appendix: source
Thrown at internal/config/store.go:1211
// Merge workspace config if present
workspacePath := filepath.Join(cfg.Options.DataDirectory, fmt.Sprintf("%s.json", appName))
if wsData, err := os.ReadFile(workspacePath); err == nil && len(wsData) > 0 {
if !json.Valid(wsData) {
return fmt.Errorf("invalid JSON in config file %s", workspacePath)
}
merged, mergeErr := loadFromBytes(append([][]byte{mustMarshalConfig(cfg)}, wsData))
if mergeErr == nil {
dataDir := cfg.Options.DataDirectory
*cfg = *merged
cfg.setDefaults(s.workingDir, dataDir)
loadedPaths = append(loadedPaths, workspacePath)
}
}
// Validate hooks after all config merging is complete so matcher
// regexes are recompiled on the reloaded config (mirrors Load).
if err := cfg.ValidateHooks(); err != nil {
return fmt.Errorf("invalid hook configuration on reload: %w", err)
}
// Save current state for potential rollback BEFORE configureProviders,
// which may write to disk via RemoveConfigField (e.g. removing stale
// OAuth providers). Capturing after would snapshot a config that has
// already been mutated, and the rollback would restore corrupted state.
oldConfig := s.Config()
oldLoadedPaths := s.loadedPaths
oldResolver := s.resolver
oldKnownProviders := s.knownProviders
oldOverrides := s.overrides
oldWorkspacePath := s.workspacePath
// Preserve runtime overrides
overrides := s.overrides
// Reapply model choices made in this instance. The global config file is
// shared, so it may now name a model a sibling instance selected; aView on GitHub (pinned to 7944b8e522)