{"record":{"id":"c05db38510f8e970","repo":"charmbracelet/crush","slug":"failed-to-reload-config-w","errorCode":null,"errorMessage":"failed to reload config: %w","messagePattern":"failed to reload config: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/store.go","lineNumber":1183,"sourceCode":"// Concurrent calls are serialised via writeMu.\nfunc (s *ConfigStore) ReloadFromDisk(ctx context.Context) error {\n\tif s.workingDir == \"\" {\n\t\treturn fmt.Errorf(\"cannot reload: working directory not set\")\n\t}\n\ts.writeMu.Lock()\n\tdefer s.writeMu.Unlock()\n\treturn s.reloadFromDiskLocked(ctx)\n}\n\n// reloadFromDiskLocked performs the actual reload. Caller must hold writeMu.\nfunc (s *ConfigStore) reloadFromDiskLocked(ctx context.Context) error {\n\t// Migrate deprecated disable_notifications before reloading config.\n\tmigrateDisableNotifications()\n\n\tconfigPaths := lookupConfigs(s.workingDir)\n\tcfg, loadedPaths, err := loadFromConfigPaths(ctx, configPaths)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to reload config: %w\", err)\n\t}\n\n\t// Apply defaults (using existing data directory if set)\n\tvar dataDir string\n\tif cur := s.Config(); cur != nil && cur.Options != nil {\n\t\tdataDir = cur.Options.DataDirectory\n\t}\n\tcfg.setDefaults(s.workingDir, dataDir)\n\n\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","sourceCodeStart":1165,"sourceCodeEnd":1201,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/config/store.go#L1165-L1201","documentation":"reloadFromDiskLocked calls loadFromConfigPaths then loadFromConfigPaths-merged loading of crushrc/crush.json files; if parsing/merging of the discovered config files fails, the error is wrapped as 'failed to reload config'. The store rolls back to its previous state.","triggerScenarios":"loadFromConfigPaths(ctx, configPaths) returns an error: malformed crushrc script or crush.json (invalid JSON, bad bash syntax in shell config), unreadable file, or a merge conflict in config structure.","commonSituations":"User just edited crush.json and left a syntax error; a crushrc with a failing command; permissions changed on a config file mid-session.","solutions":["Validate the JSON (jq . crush.json) or bash syntax (bash -n crushrc) in the files listed by lookupConfigs for the working directory.","Fix the syntax error at the reported position and reload again.","Temporarily move the offending config file aside to confirm it is the culprit, then fix and restore.","Check file permissions are readable by the process."],"exampleFix":"// before: broken crush.json\n{\"providers\": \"oops\"}\n// after\n{\"providers\": {\"anthropic\": {\"api_key\": \"sk-...\"}}}","handlingStrategy":"validation","validationCode":"for _, p := range configPaths { if strings.HasSuffix(p, \".json\") { b, _ := os.ReadFile(p); if len(b) > 0 && !json.Valid(b) { return fmt.Errorf(\"invalid JSON: %s\", p) } } }","typeGuard":null,"tryCatchPattern":"if err := store.ReloadFromDisk(ctx); err != nil {\n    var cfgErr *ConfigLoadError\n    if errors.As(err, &cfgErr) { openEditor(cfgErr.Path) }\n}","preventionTips":["Lint crush.json/crushrc in CI or an editor hook","bash -n your crushrc before saving","Keep config files under version control to diff mistakes"],"tags":["configuration","reload","json"],"backgroundTag":"config-parse-error","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}