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
- Open the policy file and ensure the top level is a JSON object: { "labels": {...}, ... }.
- Remove any array wrapper or replace scalar/null placeholder content with the expected object.
- Compare against the library's DEFAULT_POLICY shape or an existing valid policy file in the repo.
- 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
- Always export the policy as a single object, never an array or scalar.
- Add a schema check (zod/ajv) in CI for policy files.
- Start from a known-good policy template.
- Spot-check generated policies with JSON.parse before committing.
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
- candidate configuration exceeds 1 MiB
- Cannot merge ECC configuration at
- Cannot merge ECC configuration into invalid JSON at
- Expected a JSON object
- Failed to load policy from
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)