{"record":{"id":"5a0de138306d390b","repo":"charmbracelet/crush","slug":"invalid-hook-configuration-on-reload-w","errorCode":null,"errorMessage":"invalid hook configuration on reload: %w","messagePattern":"invalid hook configuration on reload: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/store.go","lineNumber":1211,"sourceCode":"\t// Merge workspace config if present\n\tworkspacePath := filepath.Join(cfg.Options.DataDirectory, fmt.Sprintf(\"%s.json\", appName))\n\tif wsData, err := os.ReadFile(workspacePath); err == nil && len(wsData) > 0 {\n\t\tif !json.Valid(wsData) {\n\t\t\treturn fmt.Errorf(\"invalid JSON in config file %s\", workspacePath)\n\t\t}\n\t\tmerged, mergeErr := loadFromBytes(append([][]byte{mustMarshalConfig(cfg)}, wsData))\n\t\tif mergeErr == nil {\n\t\t\tdataDir := cfg.Options.DataDirectory\n\t\t\t*cfg = *merged\n\t\t\tcfg.setDefaults(s.workingDir, dataDir)\n\t\t\tloadedPaths = append(loadedPaths, workspacePath)\n\t\t}\n\t}\n\n\t// Validate hooks after all config merging is complete so matcher\n\t// regexes are recompiled on the reloaded config (mirrors Load).\n\tif err := cfg.ValidateHooks(); err != nil {\n\t\treturn fmt.Errorf(\"invalid hook configuration on reload: %w\", err)\n\t}\n\n\t// Save current state for potential rollback BEFORE configureProviders,\n\t// which may write to disk via RemoveConfigField (e.g. removing stale\n\t// OAuth providers). Capturing after would snapshot a config that has\n\t// already been mutated, and the rollback would restore corrupted state.\n\toldConfig := s.Config()\n\toldLoadedPaths := s.loadedPaths\n\toldResolver := s.resolver\n\toldKnownProviders := s.knownProviders\n\toldOverrides := s.overrides\n\toldWorkspacePath := s.workspacePath\n\n\t// Preserve runtime overrides\n\toverrides := s.overrides\n\n\t// Reapply model choices made in this instance. The global config file is\n\t// shared, so it may now name a model a sibling instance selected; a","sourceCodeStart":1193,"sourceCodeEnd":1229,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/config/store.go#L1193-L1229","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: invalid regex\nhook pretooluse --matcher \"(edit|write\" --command ./check.sh\n// after\nhook pretooluse --matcher \"(edit|write)\" --command ./check.sh","handlingStrategy":"validation","validationCode":"for _, h := range cfg.Hooks { if _, err := regexp.Compile(h.Matcher); err != nil { return err }; if h.Command == \"\" { return errors.New(\"empty hook command\") } }","typeGuard":null,"tryCatchPattern":"if err := reload(); err != nil && strings.Contains(err.Error(), \"invalid hook configuration\") {\n    revertLastHookEdit(); reload()\n}","preventionTips":["Test hook matcher regexes (RE2) before committing config","Keep hook commands non-empty and executable","Validate hooks in CI with the same config schema"],"tags":["hooks","validation","regex"],"backgroundTag":"hook-validation-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}