affaan-m/ECC · error
Policy file must define labels.epic as a non-empty string
Error message
Policy file ${resolvedPath} must define labels.epic as a non-empty string What it means
loadPolicy requires the merged labels map (user labels overlaid on DEFAULT_LABELS) to contain labels.epic as a non-empty string, because the epic label is central to coordination workflows. It throws when labels.epic is missing, not a string, or an empty/whitespace-only string after merging with defaults.
Solutions
- Set labels.epic to a non-empty label name in the policy file, e.g. "epic": "epic".
- Ensure the key is spelled exactly 'epic' (not 'epics') inside labels.
- Remove the labels override entirely to fall back to DEFAULT_LABELS.
- If epic was intentionally renamed in GitHub, use the new real label name as the value (must exist in the repo).
Example fix
// before
{ "labels": { "epic": "" } }
// after
{ "labels": { "epic": "epic" } } Defensive patterns
Strategy: validation
Validate before calling
const parsed = JSON.parse(fs.readFileSync(policyPath, 'utf8'));
const epic = (parsed.labels || {}).epic;
if (typeof epic !== 'string' || !epic.trim()) throw new Error('policy.labels.epic must be a non-empty string'); Type guard
const hasValidEpicLabel = (p) => typeof p?.labels?.epic === 'string' && p.labels.epic.trim().length > 0;
Try / catch
try {
const policy = loadPolicy(p);
} catch (e) {
if (e.message.includes('labels.epic')) {
console.error(`Set labels.epic to a non-empty string in ${p}, or remove the labels override to use defaults.`);
} else { throw e; }
} Prevention
- Never set labels.epic to empty string/null as a placeholder.
- Spell the key exactly 'epic'.
- Omit the labels block entirely if you want defaults.
- Validate policy files against the expected schema in CI.
When it happens
Trigger: Calling loadPolicy with a policy file that sets labels to an object overriding epic with "", " ", null, or a non-string (e.g. a number or nested object), thereby clobbering the default epic label.
Common situations: Teams renaming labels and setting "epic": "" as a placeholder, renaming the key itself (e.g. "epics") so the epic key is lost, programmatic generation writing null for epic, or copy-pasting a partial labels block from another project.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- application config exceeds local size limit
- Asset request_id/modality does not match the bundle
- at least one genre spec is required
- bundle collections must be lists
- candidate configuration exceeds 1 MiB
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/23ef61e6cfe203d7.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/github-coordination/policy.js:79
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 },
project: {
...DEFAULT_POLICY.project,
...project,
fieldNames: { ...DEFAULT_POLICY.project.fieldNames, ...fieldNames },
},
sourcePath: resolvedPath,
};
}
module.exports = {View on GitHub (pinned to 8321021c54)