affaan-m/ECC · error · Error

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

Error message

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

What it means

Thrown by readJsonObject in scripts/lib/install/apply.js after JSON.parse succeeds but the value is not a plain object (null, array, or primitive). The apply pipeline needs an object so it can deepMergeJson (for merge-json) or iterate keys; a non-object top level is refused.

Source

Thrown at scripts/lib/install/apply.js:53

      mappings.push({
        sourceRel: operation.sourceRelativePath,
        destRel: path.relative(plan.targetRoot, operation.destinationPath),
      });
    }
  }
  return buildInstallIndex(mappings);
}

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 stateWithContentDigests(state) {
  return {
    ...state,
    operations: (state.operations || []).map(operation => {
      if (
        !operation.destinationPath
        || !fs.existsSync(operation.destinationPath)
        || !fs.statSync(operation.destinationPath).isFile()
      ) {
        return { ...operation };
      }
      return {
        ...operation,

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Open the named file and make the top level a JSON object { ... }.
  2. For empty configs use {} rather than [] or null.
  3. Re-run the install.

Example fix

// before (existing ~/.claude/.mcp.json)
[]

// after
{ "mcpServers": {} }
Defensive patterns

Strategy: type-guard

Validate before calling

function preflightJsonObject(filePath) {
  if (!fs.existsSync(filePath)) return;
  const v = JSON.parse(fs.readFileSync(filePath, 'utf8'));
  if (!(v !== null && typeof v === 'object' && !Array.isArray(v))) {
    throw new Error(`${filePath} top level must be a JSON object`);
  }
}
['.mcp.json', 'mcp.json'].forEach(f => preflightJsonObject(path.join(targetRoot, f)));

Type guard

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

Prevention

When it happens

Trigger: An existing destination .mcp.json / mcp.json that is a JSON array or scalar; a source hooks file whose top level is an array.

Common situations: User replaced .mcp.json with []; another tool wrote a bare string; a generator emitted null for an empty config.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/9de5c7995fc50087. Report an issue: GitHub.