affaan-m/ECC · error

is invalid at

Error message

${label} is invalid at ${filePath}: ${error.message}

What it means

readJsonObject() in inventory.js parses a JSON file from disk. If JSON.parse or the read fails, it wraps the underlying error message into this error carrying the label, file path, and parse/read failure reason. It distinguishes malformed/unreadable JSON from JSON that is merely the wrong top-level type.

Solutions

  1. Fix the JSON syntax at the path shown — run the file through a JSON linter or `node -e "JSON.parse(require('fs').readFileSync(p,'utf8'))"` to see the exact parse error.
  2. Restore the file from version control or a backup if it was hand-edited or corrupted.
  3. If the file is unrecoverable generated state, delete it and let the installer recreate it via a fresh install.
  4. Check file permissions/ownership if the message wraps a read/permission error rather than a parse error.

Example fix

// before (state.json)
{ "selectedModules": ["agents"], }  // trailing comma
// after
{ "selectedModules": ["agents"] }
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
function isParseableObject(p) {
  try {
    JSON.parse(fs.readFileSync(p, 'utf8'));
    return true;
  } catch {
    return false;
  }
}

Type guard

function isPlainObject(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v);
}

Try / catch

try {
  const state = readJsonObject(statePath, 'Managed Claude install-state');
} catch (err) {
  if (err.message.includes('is invalid at')) {
    console.error(`${err.message} — restore from backup or re-run install`);
    process.exitCode = 1;
  } else throw err;
}

Prevention

When it happens

Trigger: Calling readJsonObject(filePath, label) on a file containing invalid JSON (trailing commas, comments, truncation); the file cannot be read (missing file, permission error) — those throw the same wrapped message with the underlying error.message; a partially written state file from an interrupted process.

Common situations: Hand-editing Claude install state or inventory JSON and breaking syntax; a crashed installer leaving a truncated file; the file being replaced by a symlink, empty file, or lockfile artifact; encoding issues (BOM) breaking JSON.parse.

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/ee156496952b0621. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/install/inventory.js:36

    || os.homedir();
  const configDir = options.configDir
    || process.env.CLAUDE_CONFIG_DIR
    || path.join(homeDir, '.claude');
  const projectRoot = options.projectRoot || process.cwd();

  return {
    homeDir: path.resolve(homeDir),
    configDir: path.resolve(configDir),
    projectRoot: path.resolve(projectRoot),
  };
}

function readJsonObject(filePath, label) {
  let value;
  try {
    value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
  } catch (error) {
    throw new Error(`${label} is invalid at ${filePath}: ${error.message}`);
  }
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    throw new Error(`${label} is invalid at ${filePath}: expected a JSON object`);
  }
  return value;
}

function findManualClaudePlugin(options = {}) {
  const { configDir } = resolveClaudePaths(options);
  const pluginsDir = path.join(configDir, 'plugins');
  const candidates = [
    ['ecc', '.claude-plugin', 'plugin.json'],
    ['ecc', 'plugin.json'],
    ['ecc@ecc', '.claude-plugin', 'plugin.json'],
    ['ecc@ecc', 'plugin.json'],
    ['everything-claude-code', '.claude-plugin', 'plugin.json'],
    ['everything-claude-code', 'plugin.json'],
  ];

View on GitHub (pinned to 8321021c54)