{"record":{"id":"4992bf1f10cb6fe3","repo":"charmbracelet/crush","slug":"shell-config-s-produced-invalid-json","errorCode":null,"errorMessage":"shell config %s produced invalid JSON","messagePattern":"shell config (.+?) produced invalid JSON","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/load.go","lineNumber":1000,"sourceCode":"\t\tif err != nil {\n\t\t\tif os.IsNotExist(err) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to open config file %s: %w\", path, err)\n\t\t}\n\t\tif len(data) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tdir := filepath.Dir(path)\n\t\tif isShellConfig(path) {\n\t\t\tjsonBytes, err := shellconfig.LoadShellConfig(ctx, path, data)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"failed to load shell config %s: %w\", path, err)\n\t\t\t}\n\t\t\tif len(jsonBytes) > 0 {\n\t\t\t\tif !json.Valid(jsonBytes) {\n\t\t\t\t\treturn nil, nil, fmt.Errorf(\"shell config %s produced invalid JSON\", path)\n\t\t\t\t}\n\t\t\t\taddTopLevelKeys(shDirKeys, dir, jsonBytes)\n\t\t\t\tconfigs = append(configs, jsonBytes)\n\t\t\t\tloaded = append(loaded, path)\n\t\t\t}\n\t\t} else {\n\t\t\tif !json.Valid(data) {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"invalid JSON in config file %s\", path)\n\t\t\t}\n\t\t\taddTopLevelKeys(jsonDirKeys, dir, data)\n\t\t\tconfigs = append(configs, data)\n\t\t\tloaded = append(loaded, path)\n\t\t}\n\t}\n\n\t// Warn if both a JSON config and a crushrc exist in the same directory\n\t// and define overlapping top-level keys. Disjoint coexistence is\n\t// intentional and not worth warning about.","sourceCodeStart":982,"sourceCodeEnd":1018,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/config/load.go#L982-L1018","documentation":"After shellconfig.LoadShellConfig succeeds it must yield valid JSON. If the produced byte slice is non-empty but json.Valid reports invalid, loadFromConfigPaths aborts with this error. This guards against a crushrc that evaluates without error but emits malformed JSON.","triggerScenarios":"Load processes a crushrc where LoadShellConfig returns bytes that are non-empty and fail json.Valid — e.g. duplicated keys from bad merging, truncated output, or a builtin emitting non-JSON text into the config.","commonSituations":"Custom or outdated shell builtins writing raw text; encoding issues (BOM, non-UTF8 bytes) in generated JSON; a bug in a custom builtin that produces near-JSON output.","solutions":["Validate the JSON your crushrc should produce by inspecting the inner generated output or running the builtins manually","Remove or fix the builtin/option emitting the malformed output","Check for BOM or non-UTF8 characters in the crushrc and re-save as UTF-8","Update Crush — builtin emission bugs are fixed in newer releases"],"exampleFix":"# before: duplicate provider blocks emit conflicting JSON keys\nprovider a { ... }\nprovider a { ... }\n# after: declare each provider once\nprovider a { ... }","handlingStrategy":"validation","validationCode":"// after producing JSON from a custom builtin pipeline:\nif !json.Valid(jsonBytes) {\n  return fmt.Errorf(\"shell config produced invalid JSON\")\n}","typeGuard":null,"tryCatchPattern":"if err := config.Load(ctx, ...); err != nil {\n  if strings.Contains(err.Error(), \"produced invalid JSON\") {\n    // disable custom builtins and bisect which one emits bad output\n  }\n}","preventionTips":["Save config files as UTF-8 without BOM","Test custom builtins' emitted JSON with jq","Keep Crush updated for builtin emission fixes"],"tags":["config","json","shellconfig"],"backgroundTag":"shell-config-invalid-json","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}