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

  1. Set labels.epic to a non-empty label name in the policy file, e.g. "epic": "epic".
  2. Ensure the key is spelled exactly 'epic' (not 'epics') inside labels.
  3. Remove the labels override entirely to fall back to DEFAULT_LABELS.
  4. 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

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


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)