{"record":{"id":"c64cb162f457e80f","repo":"affaan-m/ECC","slug":"invalid-label-at-filepath-expected-a-json-object","errorCode":null,"errorMessage":"Invalid ${label} at ${filePath}: expected a JSON object","messagePattern":"Invalid (.+?) at (.+?): expected a JSON object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/hooks-config.js","lineNumber":43,"sourceCode":"const FINGERPRINT_PATTERN = /^[0-9a-f]{12}$/;\n\nfunction readJsonObject(filePath, label) {\n  let raw;\n  try {\n    raw = fs.readFileSync(filePath, 'utf8');\n  } catch (error) {\n    throw new Error(`Unable to read ${label} at ${filePath}: ${error.message}`);\n  }\n\n  let parsed;\n  try {\n    parsed = JSON.parse(raw);\n  } catch (error) {\n    throw new Error(`Invalid JSON in ${label} at ${filePath}: ${error.message}`);\n  }\n\n  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n    throw new Error(`Invalid ${label} at ${filePath}: expected a JSON object`);\n  }\n\n  return parsed;\n}\n\nfunction metadataPathFor(hooksPath) {\n  return path.join(path.dirname(hooksPath), METADATA_FILENAME);\n}\n\n/**\n * JSON.stringify with object keys sorted, so a fingerprint does not change when\n * someone reorders the keys inside a hook object.\n */\nfunction stableStringify(value) {\n  if (Array.isArray(value)) {\n    return `[${value.map(stableStringify).join(',')}]`;\n  }\n  if (value && typeof value === 'object') {","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/hooks-config.js#L25-L61","documentation":"After successful JSON.parse, readJsonObject() enforces that the top-level value is a non-null, non-array object. Arrays, null, strings, or numbers in a file that must hold a settings/metadata object throw 'Invalid <label> at <filePath>: expected a JSON object'. The hooks configuration model is keyed by event names, so only objects are acceptable at the root.","triggerScenarios":"Calling hooksConfig/readHooksConfig/addJsonMergeOperation against a file containing a JSON array (e.g. [] or [{...}]), null, a bare string/number, or true — syntactically valid JSON but the wrong root type.","commonSituations":"Someone replaced settings.json contents with an array of hooks; a tool exported the config as a list; a reset script wrote null; copy-paste put a JSON snippet rather than an object into the file.","solutions":["Rewrite the file so its root is an object, e.g. { \"hooks\": { ... } }.","If your data is currently an array, wrap it: { \"hooks\": { \"Stop\": [ ...entries ] } }.","If the file should be empty, use {} rather than [] or null."],"exampleFix":"// before (settings.json)\n[ { \"type\": \"command\", \"command\": \"node hook.js\" } ]\n// after\n{ \"hooks\": { \"Stop\": [ { \"hooks\": [ { \"type\": \"command\", \"command\": \"node hook.js\" } ] } ] } }","handlingStrategy":"type-guard","validationCode":"const parsed = JSON.parse(fs.readFileSync(path, 'utf8'));\nif (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n  throw new Error(`${path} must contain a JSON object at the root`);\n}","typeGuard":"function isPlainObject(v) { return v !== null && typeof v === 'object' && !Array.isArray(v); }","tryCatchPattern":"try {\n  const cfg = readHooksConfig(path);\n} catch (err) {\n  if (String(err.message).includes('expected a JSON object')) {\n    console.error(`${path} has a non-object root; wrapping or resetting it.`);\n    const v = JSON.parse(fs.readFileSync(path, 'utf8'));\n    fs.writeFileSync(path, JSON.stringify(Array.isArray(v) ? { hooks: { Stop: v } } : {}, null, 2));\n  } else throw err;\n}","preventionTips":["Always start hooks config files with { } at the root; never a bare array or null.","Export tooling should serialize the whole config object, not just the entries list.","Add a startup assertion that config roots are plain objects.","Use {} as the canonical 'empty config' content."],"tags":["json","schema-validation","type-mismatch","hooks-config"],"backgroundTag":"schema-validation-failed","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}