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

  1. Open the file at <filePath> and fix the JSON at the position given in error.message (JSON.parse messages cite line/column).
  2. Remove non-JSON constructs: comments, trailing commas, single quotes, merge-conflict markers.
  3. If the file came from a merge, re-merge properly or restore from git and re-apply changes as valid JSON.
  4. 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

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.

Related errors


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)