{"record":{"id":"082abe3701fb85b7","repo":"JuliusBrussee/caveman","slug":"verbs-is-not-an-array","errorCode":null,"errorMessage":"verbs is not an array","messagePattern":"verbs is not an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"skills/verbs-gate.mjs","lineNumber":54,"sourceCode":"  return verbs;\n}\n\n// Derive every surface a skill may reference. `errors` is non-empty when a\n// source could not be parsed — the caller must fail closed rather than scan\n// against an empty surface (which would pass everything).\nexport function loadSurfaces({ skillsDir, cliDir }) {\n  const errors = [];\n  const publicRoot = join(skillsDir, \"..\");\n  const packageRoot = existsSync(join(publicRoot, \"sdk\"))\n    ? publicRoot\n    : join(publicRoot, \"packages\");\n\n  // Top-level CLI verbs: the reserved-verbs registry is the canonical source\n  // (it already drives reserved-verbs.generated.ts).\n  let topLevel = new Set();\n  try {\n    const doc = JSON.parse(readFileSync(join(skillsDir, \"..\", \"agents\", \"reserved-verbs.json\"), \"utf8\"));\n    if (!Array.isArray(doc.verbs)) throw new Error(\"verbs is not an array\");\n    topLevel = new Set(doc.verbs);\n  } catch (error) {\n    errors.push(`reserved-verbs.json: ${error.message}`);\n  }\n\n  // `caveman tools <verb>` / `caveman cloud <verb>` namespaces.\n  const indexSrc = readOptional(join(cliDir, \"src\", \"index.ts\"));\n  const toolVerbs = discoveryVerbs(indexSrc, \"const TOOL_DISCOVERY\");\n  const cloudVerbs = discoveryVerbs(indexSrc, \"const CLOUD_DISCOVERY\");\n  if (toolVerbs.size === 0) errors.push(\"could not parse TOOL_DISCOVERY verbs from CLI index.ts\");\n  if (cloudVerbs.size === 0) errors.push(\"could not parse CLOUD_DISCOVERY verbs from CLI index.ts\");\n\n  // MCP tool names: engine server (Go) + agent-native server (TS).\n  const mcpTools = new Set();\n  let declaredEngineTools = [];\n  try {\n    const manifest = JSON.parse(readFileSync(join(skillsDir, \"engine-mcp-tools.json\"), \"utf8\"));\n    if (manifest.schema_version !== \"1\" || !Array.isArray(manifest.tools)) {","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/skills/verbs-gate.mjs#L36-L72","documentation":"Thrown inside loadSurfaces() in skills/verbs-gate.mjs while reading agents/reserved-verbs.json, the canonical registry of top-level CLI verbs. The code requires the parsed document to have a `verbs` array; a missing, null, or differently-shaped `verbs` field throws. The throw is caught immediately and recorded as `reserved-verbs.json: <message>` in the accumulated gate errors, so it surfaces as part of the verbs-gate failure report rather than a crash.","triggerScenarios":"reserved-verbs.json exists and parses as JSON but `verbs` is an object, a string, or absent (e.g. someone restructured the file into {version, verbs: {...}} or renamed the key). A pure JSON syntax error takes the same path with a different message.","commonSituations":"Editing the reserved-verbs registry and changing its shape; tooling that pretty-prints/rewrites the file and drops or renames the array; schema drift after an upgrade.","solutions":["Open agents/resolved path reserved-verbs.json and restore `verbs` as a JSON array of verb strings: `{ \"verbs\": [\"install\", \"run\", ...] }`.","Check the gate output for the companion errors — reserved-verbs drift usually co-occurs with generated-file drift (reserved-verbs.generated.ts).","Re-run the verbs gate to confirm the file passes."],"exampleFix":"// before: agents/reserved-verbs.json\n{ \"verbs\": { \"install\": true } }\n// after\n{ \"verbs\": [\"install\", \"run\", \"wrap\"] }","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isReservedVerbsDoc(doc) {\n  return doc !== null && typeof doc === \"object\" && Array.isArray(doc.verbs)\n    && doc.verbs.every((v) => typeof v === \"string\");\n}","tryCatchPattern":null,"preventionTips":["Never restructure reserved-verbs.json without updating skills/verbs-gate.mjs in the same change.","Run the verbs gate in pre-commit so registry shape errors surface immediately."],"tags":["json","validation","cli","gate"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}