affaan-m/ECC · error · Error
unknown role " "; expected one of
Error message
unknown role "${spec.role}"; expected one of ${Object.keys(ROLE_CLAUSES).join(', ')} What it means
ROLE_CLAUSES is a fixed lookup of agreement roles; buildValues() selects the clause template by spec.role. When the role is not one of the known keys, it throws this error listing the valid alternatives. The message is dynamic: it echoes the offending role and enumerates accepted values.
Solutions
- Read the error message — it lists exactly which roles are accepted.
- Change spec.role to one of the listed values, matching case exactly.
- Check the project docs/templates for the canonical role names if the list is unfamiliar.
- If the role list genuinely needs extending, add the clause to ROLE_CLAUSES in build-agreement.js rather than guessing a value.
Example fix
// before
const spec = { file: 'NDA', short: 'NDA', role: 'Contractor' };
// after
const spec = { file: 'NDA', short: 'NDA', role: 'consultant' }; // use a listed role Defensive patterns
Strategy: validation
Validate before calling
const VALID_ROLES = Object.keys(ROLE_CLAUSES); // or hardcode the known list
if (!VALID_ROLES.includes(spec.role)) {
throw new Error(`role must be one of: ${VALID_ROLES.join(', ')}`);
} Type guard
function isKnownRole(role, knownRoles) {
return typeof role === 'string' && knownRoles.includes(role);
} Try / catch
try {
buildValues(spec, now);
} catch (e) {
if (e.message.startsWith('unknown role')) {
console.error(`${e.message} — check spec.role casing and spelling`);
} else throw e;
} Prevention
- Copy role names from the error message or docs exactly; they are case-sensitive.
- Keep a shared constant of valid roles in your codebase instead of inlining strings in specs.
- Validate spec.role with an enum in your schema (zod: z.enum([...])) before calling the library.
When it happens
Trigger: spec.role = 'Contractor' (wrong case), spec.role = 'vendor' when only e.g. consultant/employee/client exist, or role omitted-but-coerced (e.g. undefined interpolated into the message).
Common situations: Typo or wrong casing in the spec JSON, following outdated documentation that used a renamed role, or copying a spec from another generator with a different role taxonomy.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- replay.bad_mode
- artifact has invalid modality binding
- assigneeKind must be 'agent' or 'human'.
- Choose at least one guided harness: Claude, Codex, or Kimi.
- CV effect has an invalid subject anchor
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/527c35dd142a703f.
Report an issue: GitHub.
Appendix: source
Thrown at skills/master-agreement-generator/scripts/build-agreement.js:96
function buildValues(spec, now) {
if (!spec || typeof spec !== 'object') {
throw new Error('spec must be an object');
}
for (const key of ['file', 'short', 'role']) {
if (typeof spec[key] !== 'string' || spec[key].trim() === '') {
throw new Error(`spec.${key} is required`);
}
}
// Reject path syntax on every host, including Windows paths supplied on POSIX.
if (/[<>:"/\\|?*\p{Cc}]/u.test(spec.file) ||
/[. ]$/.test(spec.file) ||
/^(con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(spec.file)) {
throw new Error('spec.file must be a portable filename without path components or control characters');
}
const clauses = ROLE_CLAUSES[spec.role];
if (!clauses) {
throw new Error(`unknown role "${spec.role}"; expected one of ${Object.keys(ROLE_CLAUSES).join(', ')}`);
}
const cp = spec.short;
const fill = text => text.split('{cp}').join(cp);
const supplement = typeof spec.supplement === 'string' && spec.supplement.trim() ? `${spec.supplement.trim()}; ` : '';
return {
FEE_TITLE: clauses.title,
CP_SHORT: cp,
DATE: spec.date || defaultDate(now),
CP_LEGAL: spec.legal || BLANK,
CP_JURIS: spec.juris || BLANK,
CP_ADDR: spec.addr || BLANK,
ROLE_CLAUSE: fill(clauses.role),
FEE_CLAUSE: fill(clauses.fee),
SCHEDULE_ROWS: renderScheduleRows(spec.schedule),
SUPPLEMENT_CLAUSE: supplement,
CP_SIGBLOCK: (spec.legal || cp).toUpperCase(),
CP_SIGNER: spec.signer || BLANK,View on GitHub (pinned to 8321021c54)