{"record":{"id":"a28ef0a7f21f8dd1","repo":"tinyhumansai/openhuman","slug":"must-be-an-array","errorCode":null,"errorMessage":"must be an array","messagePattern":"must be an array","errorType":"validation","errorClass":"SpecError","httpStatus":null,"severity":"error","filePath":"scripts/agent-batch/lib.mjs","lineNumber":168,"sourceCode":"          `${at}.owned_paths[${j}]`,\n        );\n      }\n      if (p.includes(\"*\") || p.includes(\"?\")) {\n        throw new SpecError(\n          `globs not allowed — use directory prefixes (got \"${p}\")`,\n          `${at}.owned_paths[${j}]`,\n        );\n      }\n      if (p.startsWith(\"/\")) {\n        throw new SpecError(\n          `paths must be repo-relative, not absolute (got \"${p}\")`,\n          `${at}.owned_paths[${j}]`,\n        );\n      }\n    }\n    if (\"allowed_shared_paths\" in agent) {\n      if (!Array.isArray(agent.allowed_shared_paths)) {\n        throw new SpecError(\"must be an array\", `${at}.allowed_shared_paths`);\n      }\n    }\n    if (\"labels\" in agent && !Array.isArray(agent.labels)) {\n      throw new SpecError(\"must be an array\", `${at}.labels`);\n    }\n  }\n  return spec;\n}\n\n// Pure overlap detection: returns an array of collisions. Each entry is\n// { a, b, pathA, pathB, reason } where reason is \"prefix\" (one contains the\n// other) or \"exact\" (identical). Empty array = disjoint.\n//\n// Shared paths in allowed_shared_paths are NOT considered collisions — they\n// are escape hatches for unavoidably-shared files like the coverage matrix.\nexport function findOverlaps(spec) {\n  const collisions = [];\n  for (let i = 0; i < spec.agents.length; i++) {","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/agent-batch/lib.mjs#L150-L186","documentation":"Thrown by validateSpec() in scripts/agent-batch/lib.mjs when an agent carries an allowed_shared_paths field that is not an array. allowed_shared_paths is the optional per-agent escape hatch that exempts specific paths from findOverlaps() collision detection (e.g. a shared coverage matrix), so it must be a list even when it names a single file.","triggerScenarios":"A spec with \"allowed_shared_paths\": \"docs/coverage-matrix.md\" (a bare string) or an object/number at agents[i]. The field is only inspected when present (\"allowed_shared_paths\" in agent), so omitting it entirely never triggers this.","commonSituations":"Spec authors shorten a one-element list to a scalar out of habit, or YAML-to-JSON converters collapse a single-item list. Because the sibling owned_paths field is required and heavily validated, people assume the same string form is fine here too.","solutions":["Wrap the value in an array: \"allowed_shared_paths\": [\"docs/coverage-matrix.md\"]","If the field is not needed for this agent, delete it entirely — it is optional","Re-run the agent-batch command; validation reports the exact agents[i] index in the error path"],"exampleFix":"// before\n\"allowed_shared_paths\": \"docs/matrix.md\"\n\n// after\n\"allowed_shared_paths\": [\"docs/matrix.md\"]","handlingStrategy":"validation","validationCode":"const ok = spec.agents.every(\n  (a) => !(\"allowed_shared_paths\" in a) || Array.isArray(a.allowed_shared_paths),\n);\nif (!ok) throw new Error(\"allowed_shared_paths must always be an array when present\");","typeGuard":"function isPathList(v) {\n  return v === undefined || (Array.isArray(v) && v.every((p) => typeof p === \"string\" && p.length > 0));\n}","tryCatchPattern":null,"preventionTips":["Normalize specs in CI: a lint step that coerces scalar strings to [string] or rejects them","Remember both optional list fields (allowed_shared_paths, labels) share the same array rule — write them identically"],"tags":["validation","config","cli"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}