affaan-m/ECC · error · Error

Invalid at : expected a JSON object

Error message

Invalid ${label} at ${filePath}: expected a JSON object

What it means

After a successful JSON.parse in readJsonObject(), the top-level value must be a plain JSON object. This error fires when the parsed root is anything else - an array, string, number, boolean or null - because downstream merge operations (deepMergeJson) need an object root to merge into.

Solutions

  1. Make the top-level value an object: wrap the array under a named key
  2. Compare against the object shape in ECC's shipped example configs
  3. Re-run the install after fixing; the merge will then apply key-by-key

Example fix

// before (top-level array)
[ { "matcher": "Bash" } ]

// after
{ "hooks": { "PreToolUse": [ { "matcher": "Bash" } ] } }
Defensive patterns

Strategy: validation

Validate before calling

const value = JSON.parse(fs.readFileSync(payloadPath, 'utf8'));
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
  throw new Error(`${payloadPath}: top-level value must be a JSON object`);
}

Type guard

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

Try / catch

try {
  resolveInstallPlan(options);
} catch (error) {
  if (/expected a JSON object/.test(error.message)) {
    // the named file parses but has a non-object root; wrap arrays under a key
  } else {
    throw error;
  }
}

Prevention

When it happens

Trigger: A hooks/MCP config whose top level is a JSON array (e.g. a bare list of servers); a payload file containing just a quoted string or a number; a file containing null.

Common situations: Users replacing an object-shaped config with a list; scaffolding or examples that emit arrays; misunderstanding of the expected config schema.

Related errors


AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18). Data as JSON: /api/errors/91ddf37dfa22351f. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/install-executor.js:215

      sourceRelativePath: options.sourceRelativePath,
      destinationPath: options.destinationPath,
      strategy: options.strategy || 'preserve-relative-path'
    })
  );

  return true;
}

function readJsonObject(filePath, label) {
  let parsed;
  try {
    parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
  } catch (error) {
    throw new Error(`Failed to parse ${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 addCursorAgentDataScaffoldOperations(operations, options) {
  const scaffoldRoot = path.join(options.sourceRoot, 'scaffolds', 'cursor');
  if (!fs.existsSync(scaffoldRoot)) {
    return;
  }

  addFileCopyOperation(operations, {
    moduleId: options.moduleId,
    sourceRoot: options.sourceRoot,
    sourceRelativePath: path.join('scaffolds', 'cursor', 'ecc-agent-data.json'),
    destinationPath: path.join(options.targetRoot, 'ecc-agent-data.json'),
    strategy: 'preserve-relative-path'
  });

View on GitHub (pinned to 06c5e118c4)