affaan-m/ECC · error · Error
orch-review: args.changedFiles must be an array of paths
Error message
orch-review: args.changedFiles must be an array of paths
What it means
When provided, args.changedFiles must be an array (of string paths). Any other type — string, object, number — is rejected because the field feeds the security-trigger haystack, and a malformed value could silently distort the security review rather than review it correctly.
Solutions
- Convert the value to an array of strings: changedFiles: ['a.js', 'b.js'].
- For a single file, wrap it in an array instead of passing the bare string.
- Fix serialization so Sets/Maps become arrays before invoking the workflow.
Example fix
// before
review({ diff, changedFiles: 'src/a.js' });
// after
review({ diff, changedFiles: ['src/a.js'] }); Defensive patterns
Strategy: validation
Validate before calling
if (changedFiles != null && !Array.isArray(changedFiles)) {
throw new Error('changedFiles must be an array of path strings (or omitted)');
} Type guard
const isChangedFiles = v => v == null || (Array.isArray(v) && v.every(f => typeof f === 'string'));
Try / catch
try {
const result = await orchReview({ diff, changedFiles });
} catch (err) {
if (err.message.includes('changedFiles must be an array of paths')) {
console.error('Normalize changedFiles to a string[] before invoking.');
} else throw err;
} Prevention
- Omit changedFiles when unknown — it is optional — rather than passing a wrong-typed value.
- Serialize Sets/Maps to arrays before building the payload.
- Keep the payload schema (diff: string, changedFiles?: string[]) documented next to the caller.
When it happens
Trigger: Calling with { diff: '...', changedFiles: 'a.js' } (single string), changedFiles: { path: 'a.js' }, or changedFiles: 5; also changedFiles set to a Set or other non-array iterable from the producer.
Common situations: Callers mimicking another tool's API that takes a single path string; serializing a Map/Set to JSON which collapses to an object; passing the output of a diff parser that yields an object keyed by filename.
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
- orch-review: args.changedFiles must contain only string…
- orch-review: args must be an object
- Canonical session snapshot must be an object
- Canonical session snapshot requires
- Canonical session snapshot requires
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/26a6f220efa5048e.
Report an issue: GitHub.
Appendix: source
Thrown at workflows/orch-review.workflow.js:165
// `args` arrives verbatim. Accept a JSON-encoded string too, so the workflow
// works whether the caller passes an object or a stringified payload.
// Fail CLOSED on invalid input: a review gate must never silently APPROVE a
// payload it could not actually review.
let input;
try {
input = typeof args === 'string' ? JSON.parse(args) : (args ?? {});
} catch {
throw new Error('orch-review: args must be an object or valid JSON');
}
if (typeof input !== 'object' || input === null) {
throw new Error('orch-review: args must be an object');
}
if (typeof input.diff !== 'string' || input.diff.trim() === '') {
throw new Error('orch-review: args.diff must be a non-empty unified diff');
}
if (input.changedFiles != null && !Array.isArray(input.changedFiles)) {
throw new Error('orch-review: args.changedFiles must be an array of paths');
}
// Every entry must be a string path. A non-string (e.g. { path: '...' }) would
// stringify to "[object Object]" and silently poison the security-trigger
// haystack — fail closed on malformed input instead.
if (Array.isArray(input.changedFiles) && !input.changedFiles.every(f => typeof f === 'string')) {
throw new Error('orch-review: args.changedFiles must contain only string paths');
}
const diff = input.diff;
const haystack = `${diff}\n${(input.changedFiles || []).join('\n')}`;
// Build the review dimensions immutably. Quality always runs; language +
// security are conditional, spread in rather than pushed onto a shared array.
const langReviewer = input.language && LANGUAGE_REVIEWER[String(input.language).toLowerCase()];
const securityNeeded = SECURITY_TRIGGER.test(haystack);
const dimensions = [
{ key: 'quality', label: 'correctness & quality', agentType: 'ecc:code-reviewer' },
...(langReviewer ? [{ key: `lang:${input.language}`, label: `${input.language} idioms & pitfalls`, agentType: langReviewer }] : []),View on GitHub (pinned to 8321021c54)