{"record":{"id":"90ec879630d34b92","repo":"tinyhumansai/openhuman","slug":"must-be-a-non-empty-string","errorCode":null,"errorMessage":"must be a non-empty string","messagePattern":"must be a non-empty string","errorType":"validation","errorClass":"SpecError","httpStatus":null,"severity":"error","filePath":"scripts/agent-batch/lib.mjs","lineNumber":148,"sourceCode":"      throw new SpecError(\n        `branch issue segment \"${m[2]}\" does not match agent issue ${agent.issue}`,\n        `${at}.branch`,\n      );\n    }\n    if (seenBranch.has(agent.branch)) {\n      throw new SpecError(`duplicate branch \"${agent.branch}\"`, `${at}.branch`);\n    }\n    seenBranch.add(agent.branch);\n    if (!Array.isArray(agent.owned_paths) || agent.owned_paths.length === 0) {\n      throw new SpecError(\n        \"owned_paths must be a non-empty array\",\n        `${at}.owned_paths`,\n      );\n    }\n    for (let j = 0; j < agent.owned_paths.length; j++) {\n      const p = agent.owned_paths[j];\n      if (typeof p !== \"string\" || p.length === 0) {\n        throw new SpecError(\n          \"must be a non-empty string\",\n          `${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) {","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/agent-batch/lib.mjs#L130-L166","documentation":"Each element of agent.owned_paths must be a string of length > 0 — checked inside the per-path loop with index-precise path \"agents[i].owned_paths[j]\". This is purely type/emptiness; format policy (no globs, not absolute) is enforced by the following checks. Note it checks length only, so a whitespace-only path passes here.","triggerScenarios":"owned_paths containing \"\" (empty string, often from a trailing comma in a generator join), null, a number, or undefined holes in a sparse array.","commonSituations":"Generating paths with split(\"\n\") or filter steps that leave empty strings; JSON arrays with explicit null entries; trailing separators producing one empty final element.","solutions":["Remove the empty entry at the reported index (e.g. agents[1].owned_paths[2])","Sanitize before validating: owned_paths = owned_paths.filter(p => typeof p === \"string\" && p.length > 0)","Fix the generator so joins/splits do not emit empty elements"],"exampleFix":"// before\n\"owned_paths\": [\"src/openhuman/meet/\", \"\"]\n\n// after\n\"owned_paths\": [\"src/openhuman/meet/\"]","handlingStrategy":"validation","validationCode":"for (const [i, a] of spec.agents.entries()) {\n  a.owned_paths = a.owned_paths.filter((p) => typeof p === \"string\" && p.length > 0);\n  if (a.owned_paths.length === 0) { /* now error 197 semantics */ }\n}","typeGuard":"/** @param {unknown} v */\nfunction isNonEmptyString(v) {\n  return typeof v === \"string\" && v.length > 0;\n}","tryCatchPattern":"try {\n  validateSpec(spec);\n} catch (e) {\n  if (e instanceof SpecError && e.path?.includes(\".owned_paths[\")) {\n    const idx = /owned_paths\\[(\\d+)]/.exec(e.path ?? \"\")[1];\n    console.error(`remove/fix the empty entry at ${e.path}`);\n    process.exit(1);\n  }\n  throw e;\n}","preventionTips":["Filter empty strings out of generated path lists before serializing the spec","Avoid split(\"\\n\") on path lists without a trailing filter for empty lines"],"tags":["validation","json","spec","agent-batch","ownership"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}