{"record":{"id":"7565305b15ae2f5b","repo":"affaan-m/ECC","slug":"orch-review-args-changedfiles-must-contain-only-string-paths","errorCode":null,"errorMessage":"orch-review: args.changedFiles must contain only string paths","messagePattern":"orch-review: args\\.changedFiles must contain only string paths","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"workflows/orch-review.workflow.js","lineNumber":171,"sourceCode":"try {\n  input = typeof args === 'string' ? JSON.parse(args) : (args ?? {});\n} catch {\n  throw new Error('orch-review: args must be an object or valid JSON');\n}\nif (typeof input !== 'object' || input === null) {\n  throw new Error('orch-review: args must be an object');\n}\nif (typeof input.diff !== 'string' || input.diff.trim() === '') {\n  throw new Error('orch-review: args.diff must be a non-empty unified diff');\n}\nif (input.changedFiles != null && !Array.isArray(input.changedFiles)) {\n  throw new Error('orch-review: args.changedFiles must be an array of paths');\n}\n// Every entry must be a string path. A non-string (e.g. { path: '...' }) would\n// stringify to \"[object Object]\" and silently poison the security-trigger\n// haystack — fail closed on malformed input instead.\nif (Array.isArray(input.changedFiles) && !input.changedFiles.every(f => typeof f === 'string')) {\n  throw new Error('orch-review: args.changedFiles must contain only string paths');\n}\n\nconst diff = input.diff;\nconst haystack = `${diff}\\n${(input.changedFiles || []).join('\\n')}`;\n\n// Build the review dimensions immutably. Quality always runs; language +\n// security are conditional, spread in rather than pushed onto a shared array.\nconst langReviewer = input.language && LANGUAGE_REVIEWER[String(input.language).toLowerCase()];\nconst securityNeeded = SECURITY_TRIGGER.test(haystack);\nconst dimensions = [\n  { key: 'quality', label: 'correctness & quality', agentType: 'ecc:code-reviewer' },\n  ...(langReviewer ? [{ key: `lang:${input.language}`, label: `${input.language} idioms & pitfalls`, agentType: langReviewer }] : []),\n  ...(securityNeeded ? [{ key: 'security', label: 'security (OWASP, secrets, injection)', agentType: 'ecc:security-reviewer' }] : [])\n];\nif (securityNeeded) {\n  log('Security trigger matched — adding security-reviewer dimension.');\n}\n","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/workflows/orch-review.workflow.js#L153-L189","documentation":"Even when changedFiles is an array, every entry must be a plain string path. A non-string entry (e.g. { path: '...' } object or a number) would stringify to \"[object Object]\" and poison the security-trigger haystack, so the workflow fails closed rather than reviewing corrupted input.","triggerScenarios":"Calling with { diff: '...', changedFiles: [{ path: 'a.js' }] } or changedFiles: [1, 2] — the array check passes but changedFiles.every(f => typeof f === 'string') fails.","commonSituations":"Passing file objects straight from a VCS/API client (GitHub files API returns { filename, status } objects); including line-number metadata tuples; mixing parsed diff entries with raw path strings.","solutions":["Map entries to strings before invoking: changedFiles.map(f => typeof f === 'string' ? f : f.path).","Validate the producer output shape and normalize to string paths at the boundary.","Add a caller-side check `files.every(f => typeof f === 'string')` before invoking the workflow."],"exampleFix":"// before\nreview({ diff, changedFiles: githubFiles }); // [{ filename: 'a.js' }]\n// after\nreview({ diff, changedFiles: githubFiles.map(f => f.filename) });","handlingStrategy":"type-guard","validationCode":"const files = (githubFiles ?? []).map(f => typeof f === 'string' ? f : f.filename);\nif (!files.every(f => typeof f === 'string')) {\n  throw new Error('changedFiles entries must be string paths');\n}","typeGuard":"const isStringPathArray = v => Array.isArray(v) && v.every(f => typeof f === 'string');","tryCatchPattern":"try {\n  const result = await orchReview({ diff, changedFiles: files });\n} catch (err) {\n  if (err.message.includes('must contain only string paths')) {\n    console.error('Map file objects to their .filename/.path before invoking.');\n  } else throw err;\n}","preventionTips":["Normalize VCS/API file objects ({ filename }) to plain string paths at the boundary.","Add a schema check (zod: z.array(z.string())) on the payload before invoking.","Never pass diff-parser entries or tuples where plain paths are expected."],"tags":["validation","type-mismatch","fail-closed"],"backgroundTag":"schema-validation-failed","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}