affaan-m/ECC · error

Policy file must contain a JSON object, got

Error message

Policy file ${resolvedPath} must contain a JSON object, got ${Array.isArray(parsed) ? 'array' : typeof parsed}

What it means

loadPolicy throws when the parsed policy file is valid JSON but not a plain JSON object — i.e. it is null, an array, or a primitive (string/number/boolean). The policy schema requires an object whose keys (labels, review, validation, branchModel, project) are merged over DEFAULT_POLICY, so any other top-level type is unusable.

Solutions

  1. Open the policy file and ensure the top level is a JSON object: { "labels": {...}, ... }.
  2. Remove any array wrapper or replace scalar/null placeholder content with the expected object.
  3. Compare against the library's DEFAULT_POLICY shape or an existing valid policy file in the repo.
  4. Validate with a quick script: JSON.parse(content) must satisfy typeof parsed === 'object' && !Array.isArray(parsed) && parsed !== null.

Example fix

// before
[{ "labels": { "epic": "epic" } }]
// after
{ "labels": { "epic": "epic" } }
Defensive patterns

Strategy: type-guard

Validate before calling

const parsed = JSON.parse(fs.readFileSync(policyPath, 'utf8'));
const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
if (!isPlainObject(parsed)) throw new Error(`Policy must be a JSON object, got ${Array.isArray(parsed) ? 'array' : typeof parsed}`);

Type guard

const isPolicyObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v) && typeof v.labels === 'object';

Try / catch

try {
  const policy = loadPolicy(p);
} catch (e) {
  if (e.message.includes('must contain a JSON object')) {
    console.error(`Rewrite ${p} so its top level is an object, not ${/got (.+)$/.exec(e.message)[1]}.`);
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling loadPolicy with a policy file whose top-level value is an array (e.g. [ {...} ]), a bare string/number, or the literal null — often from exporting the wrong structure or wrapping the object in an array.

Common situations: Someone exported Array.of(settings) or JSON.stringify of a list, a YAML-to-JSON conversion produced a list, an edit replaced the object with a scalar placeholder, or a template file was left as `null`.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/bbe3e0dd1c646d88. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/github-coordination/policy.js:69

  const resolvedPath = configPath
    ? path.resolve(configPath)
    : path.join(rootDir, 'config', DEFAULT_CONFIG_FILE);

  if (!fs.existsSync(resolvedPath)) {
    return {
      ...DEFAULT_POLICY,
      sourcePath: null,
    };
  }

  let parsed;
  try {
    parsed = JSON.parse(fs.readFileSync(resolvedPath, 'utf8'));
  } catch (error) {
    throw new Error(`Failed to load policy from ${resolvedPath}: ${error.message}`);
  }
  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
    throw new Error(`Policy file ${resolvedPath} must contain a JSON object, got ${Array.isArray(parsed) ? 'array' : typeof parsed}`);
  }
  const labels = typeof parsed.labels === 'object' && parsed.labels !== null && !Array.isArray(parsed.labels) ? parsed.labels : {};
  const review = typeof parsed.review === 'object' && parsed.review !== null && !Array.isArray(parsed.review) ? parsed.review : {};
  const validation = typeof parsed.validation === 'object' && parsed.validation !== null && !Array.isArray(parsed.validation) ? parsed.validation : {};
  const branchModel = typeof parsed.branchModel === 'object' && parsed.branchModel !== null && !Array.isArray(parsed.branchModel) ? parsed.branchModel : {};
  const project = typeof parsed.project === 'object' && parsed.project !== null && !Array.isArray(parsed.project) ? parsed.project : {};
  const fieldNames = typeof project.fieldNames === 'object' && project.fieldNames !== null && !Array.isArray(project.fieldNames) ? project.fieldNames : {};
  const mergedLabels = { ...DEFAULT_LABELS, ...labels };
  if (typeof mergedLabels.epic !== 'string' || !mergedLabels.epic.trim()) {
    throw new Error(`Policy file ${resolvedPath} must define labels.epic as a non-empty string`);
  }
  return {
    ...DEFAULT_POLICY,
    ...parsed,
    labels: mergedLabels,
    review: { ...DEFAULT_POLICY.review, ...review },
    validation: { ...DEFAULT_POLICY.validation, ...validation },
    branchModel: { ...DEFAULT_POLICY.branchModel, ...branchModel },

View on GitHub (pinned to 8321021c54)