affaan-m/ECC · error
Invalid JSON in at
Error message
Invalid JSON in ${label} at ${filePath}: ${error.message} What it means
readJsonObject() parses the file contents with JSON.parse; if parsing fails it throws 'Invalid JSON in <label> at <filePath>: <reason>'. The library requires strict, well-formed JSON (no comments, no trailing commas, no JS syntax) for hooks settings and metadata sidecar files, and surfaces the parser's message with the file location.
Solutions
- Open the file at <filePath> and fix the JSON at the position given in error.message (JSON.parse messages cite line/column).
- Remove non-JSON constructs: comments, trailing commas, single quotes, merge-conflict markers.
- If the file came from a merge, re-merge properly or restore from git and re-apply changes as valid JSON.
- Validate with `node -e "JSON.parse(require('fs').readFileSync('<filePath>','utf8'))"` before rerunning.
Example fix
// before (.claude/settings.json)
{ "hooks": { "Stop": [] }, } // trailing comma -> Invalid JSON
// after
{ "hooks": { "Stop": [] } } Defensive patterns
Strategy: try-catch
Validate before calling
const fs = require('fs');
function isParsableJson(p) {
try { JSON.parse(fs.readFileSync(p, 'utf8')); return true; } catch (e) { return false; }
}
if (!isParsableJson('.claude/settings.json')) {
console.error('settings.json is not valid JSON; fix it before running hooks tooling');
} Type guard
function isJsonObjectText(text) { try { const v = JSON.parse(text); return v !== null && typeof v === 'object' && !Array.isArray(v); } catch { return false; } } Try / catch
try {
const cfg = readHooksConfig(path);
} catch (err) {
if (String(err.message).startsWith('Invalid JSON in')) {
console.error(`${err.message}\nTip: JSON.parse messages cite line/column; remove comments and trailing commas.`);
// optionally back up and reset: fs.copyFileSync(path, path + '.bak'); fs.writeFileSync(path, '{}');
} else throw err;
} Prevention
- Never put comments or trailing commas in .json files — use a .jsonc-aware editor warning or a JSON linter.
- Run `node -e "JSON.parse(require('fs').readFileSync(f,'utf8'))"` as a pre-commit check on config files.
- Resolve merge conflicts fully; grep for conflict markers before committing configs.
- Avoid hand-editing generated config files; use the ECC tooling.
When it happens
Trigger: Any caller (hooksConfig, readHooksConfig, addJsonMergeOperation) reading a file whose content is not valid JSON — hand-edited settings with a trailing comma or comment, truncated write, merge-conflict markers (<<<<<<<) left in the file, or a JSON5/JSONC file passed where strict JSON is expected.
Common situations: Manual edits to .claude/settings.json or hooks.json introducing syntax errors; git merge conflicts resolved but markers forgotten; editor saved with comments in a .json file; partial disk write leaving truncated JSON.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Cannot merge ECC configuration into invalid JSON at
- Failed to parse
- Invalid JSON in
- candidate configuration exceeds 1 MiB
- Cannot merge ECC configuration at
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/da1937228389d17c.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/hooks-config.js:39
const HOOKS_FILENAME = 'hooks.json';
const METADATA_FILENAME = 'hooks.metadata.json';
const FINGERPRINT_LENGTH = 12;
const FINGERPRINT_PATTERN = /^[0-9a-f]{12}$/;
function readJsonObject(filePath, label) {
let raw;
try {
raw = fs.readFileSync(filePath, 'utf8');
} catch (error) {
throw new Error(`Unable to read ${label} at ${filePath}: ${error.message}`);
}
let parsed;
try {
parsed = JSON.parse(raw);
} catch (error) {
throw new Error(`Invalid JSON in ${label} at ${filePath}: ${error.message}`);
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`Invalid ${label} at ${filePath}: expected a JSON object`);
}
return parsed;
}
function metadataPathFor(hooksPath) {
return path.join(path.dirname(hooksPath), METADATA_FILENAME);
}
/**
* JSON.stringify with object keys sorted, so a fingerprint does not change when
* someone reorders the keys inside a hook object.
*/
function stableStringify(value) {View on GitHub (pinned to 8321021c54)