{"record":{"id":"b1554b8516f918af","repo":"Egonex-AI/Understand-Anything","slug":"json-changed-file-list-must-be-an-array-of-strings","errorCode":null,"errorMessage":"JSON changed-file list must be an array of strings","messagePattern":"JSON changed-file list must be an array of strings","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"understand-anything-plugin/skills/understand/compute-batches.mjs","lineNumber":270,"sourceCode":"  for (const b of allBatches) {\n    for (const f of b.files) m.set(f.path, b.batchIndex);\n  }\n  return m;\n}\n\nfunction normalizeRelativePathForMatch(pathText) {\n  if (typeof pathText !== 'string') return '';\n  const platformPath = process.platform === 'win32' ? pathText.replace(/\\\\/g, '/') : pathText;\n  return platformPath\n    .replace(/^\\.\\/+/, '')\n    .replace(/\\/+/g, '/');\n}\n\nfunction parseChangedFileList(content, filePath) {\n  if (filePath.toLowerCase().endsWith('.json') || content.trimStart().startsWith('[')) {\n    const parsed = JSON.parse(content);\n    if (!Array.isArray(parsed) || parsed.some(path => typeof path !== 'string')) {\n      throw new Error('JSON changed-file list must be an array of strings');\n    }\n    return parsed.map(normalizeRelativePathForMatch).filter(Boolean);\n  }\n  // Backwards compatibility for existing newline-delimited callers. New\n  // incremental handoffs use JSON so embedded newlines remain unambiguous.\n  return content.split(/\\r?\\n/).map(normalizeRelativePathForMatch).filter(Boolean);\n}\n\n// ECMAScript string comparison uses a stable UTF-16 code-unit order and does\n// not depend on the host locale or ICU version.\nfunction comparePaths(a, b) {\n  if (a === b) return 0;\n  return a < b ? -1 : 1;\n}\n\n/**\n * Returns Map<path, communityId> via Louvain. May throw — caller must catch\n * and fall back if it does. Honors UA_COMPUTE_BATCHES_FORCE_LOUVAIN_THROW=1","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/Egonex-AI/Understand-Anything/blob/07edf82a04371b6f69779b067bdc8a1a8753a9db/understand-anything-plugin/skills/understand/compute-batches.mjs#L252-L288","documentation":"parseChangedFileList accepts a changed-files handoff in two formats: a JSON array of path strings, or (legacy) a newline-delimited list. When the content looks like JSON (a .json file or text starting with '[') but the parsed value is not an array, or the array contains non-string elements, this error is thrown rather than silently treating bad data as a file list.","triggerScenarios":"Calling parseChangedFileList with content that is a JSON object (e.g. `{\"files\": [...]}`) instead of a bare array; JSON array containing numbers/objects/null entries; content that begins with '[' but is malformed enough to parse into a non-array.","commonSituations":"An incremental-pipeline step wraps the array in an envelope object instead of emitting a top-level array; a hand-edited JSON file includes numeric statuses mixed with paths; an upstream tool writes `{changed: [...]}`; a diff script writes one JSON object per line.","solutions":["Change the producer to emit a top-level JSON array of path strings, e.g. [\"src/a.ts\",\"src/b.ts\"] — not an object wrapping the array.","Ensure every element is a string: filter out nulls/numbers before serializing.","If you intended the legacy newline format, remove the leading '[' / .json extension so the newline-delimited path is used.","Validate the JSON with `Array.isArray(parsed) && parsed.every(p => typeof p === 'string')` in the producing script before writing the file."],"exampleFix":"// before (changed-files.json)\n{ \"changed\": [\"src/a.ts\", 42] }\n\n// after\n[\"src/a.ts\", \"src/b.ts\"]","handlingStrategy":"validation","validationCode":"function validateChangedFileList(content) {\n  const trimmed = content.trimStart();\n  if (trimmed.startsWith('[') || filePath.toLowerCase().endsWith('.json')) {\n    const parsed = JSON.parse(content);\n    if (!Array.isArray(parsed) || !parsed.every(p => typeof p === 'string')) {\n      throw new Error('changed-file list must be a JSON array of path strings');\n    }\n  }\n}","typeGuard":"function isChangedFileList(value) {\n  return Array.isArray(value) && value.every(p => typeof p === 'string');\n}","tryCatchPattern":"try {\n  const changed = parseChangedFileList(content, filePath);\n} catch (err) {\n  if (err.message.includes('array of strings')) {\n    console.error(`${filePath}: expected [\"src/a.ts\", ...] — got ${describe(content)}`);\n    // fall back to regenerating the changed-file list from git diff\n  } else {\n    throw err;\n  }\n}","preventionTips":["Emit a top-level JSON array only — never wrap in `{ changed: [...] }`.","Filter non-string entries (statuses, nulls) before JSON.stringify in the producer.","Use JSON (not newline-delimited) for any path lists that could contain spaces or newlines.","Add a CI check that parses every handoff file with the same validator the consumer uses."],"tags":["json","validation","input-shape","diff"],"backgroundTag":"schema-validation-failed","analyzedSha":"07edf82a04371b6f69779b067bdc8a1a8753a9db","analyzedAt":"2026-09-07T23:20:10.829Z","contentChangedAt":"2026-09-07T23:20:10.829Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}