{"record":{"id":"b8d73fbc03b56574","repo":"tinyhumansai/openhuman","slug":"paths-must-be-repo-relative-not-absolute-got","errorCode":null,"errorMessage":"paths must be repo-relative, not absolute (got \"${p}\")","messagePattern":"paths must be repo-relative, not absolute \\(got \"(.+?)\"\\)","errorType":"validation","errorClass":"SpecError","httpStatus":null,"severity":"error","filePath":"scripts/agent-batch/lib.mjs","lineNumber":160,"sourceCode":"        `${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) {\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","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/agent-batch/lib.mjs#L142-L178","documentation":"Thrown by validateSpec() in scripts/agent-batch/lib.mjs while validating an agent-batch spec. Each agents[].owned_paths entry must be a repo-relative directory prefix; an entry starting with '/' is rejected because ownership tracking and the findOverlaps() collision check operate on paths relative to the repo root, where an absolute path would never match and would silently break disjointness guarantees.","triggerScenarios":"A spec JSON where any agents[i].owned_paths[j] string starts with '/', e.g. \"/home/user/openhuman/src/agent\" or \"/Users/me/repo/docs\". validateSpec() is called by every agent-batch subcommand (launch/status) right after loadSpec(), so the very first run against such a spec fails at agents[i].owned_paths[j] with the offending path echoed.","commonSituations":"Authors generate the spec from shell output ($PWD-prefixed paths), paste paths from an editor's 'copy absolute path' action, or port a spec from a machine-specific CI layout. The check sits directly after the glob check, so 'src/*' fails first with a different message; only clean-but-absolute paths reach this throw.","solutions":["Strip the repo-root prefix from the entry so it is relative, e.g. \"/home/me/openhuman/src/agent\" → \"src/agent\"","If you generated the spec programmatically, re-generate it with paths relative to the repo root (path.relative(repoRoot, p)) and re-run","Confirm no other owned_paths entries have the same problem — validation stops at the first offender, so fix and re-run until the spec passes"],"exampleFix":"// spec.json — before\n\"owned_paths\": [\"/home/me/openhuman/src/agent\"]\n\n// after\n\"owned_paths\": [\"src/agent\"]","handlingStrategy":"validation","validationCode":"// Run before handing the spec to any agent-batch command\nimport { readFileSync } from \"node:fs\";\nconst spec = JSON.parse(readFileSync(\"batch.json\", \"utf8\"));\nfor (const [i, agent] of spec.agents.entries()) {\n  for (const [j, p] of agent.owned_paths.entries()) {\n    if (typeof p !== \"string\" || p.length === 0) throw new Error(`agents[${i}].owned_paths[${j}]: empty`);\n    if (p.startsWith(\"/\")) throw new Error(`agents[${i}].owned_paths[${j}]: absolute path ${p}`);\n  }\n}","typeGuard":"function isRepoRelativePath(p) {\n  return typeof p === \"string\" && p.length > 0 && !p.includes(\"*\") && !p.includes(\"?\") && !p.startsWith(\"/\");\n}","tryCatchPattern":"try {\n  validateSpec(spec);\n} catch (e) {\n  if (e instanceof SpecError) {\n    console.error(`spec invalid at ${e.path}: ${e.message}`);\n    process.exit(2);\n  }\n  throw e;\n}","preventionTips":["Generate owned_paths with path.relative(repoRoot, absPath) instead of hand-typing them","Treat the SpecError.path field as the address to fix — it names the exact agents[i].owned_paths[j] slot","Keep a known-good spec as a template and diff new specs against it"],"tags":["validation","config","paths","cli"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}