{"record":{"id":"39b4133df7cb21d4","repo":"gethomepage/homepage","slug":"services-yaml-must-contain-a-top-level-array","errorCode":null,"errorMessage":"services.yaml must contain a top-level array","messagePattern":"services\\.yaml must contain a top-level array","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/utils/mcp/homepage-mcp.js","lineNumber":196,"sourceCode":"\n  if (typeof args.group !== \"string\" || !args.group.trim()) {\n    throw new Error(\"group must be a non-empty string\");\n  }\n  if (typeof args.name !== \"string\" || !args.name.trim()) {\n    throw new Error(\"name must be a non-empty string\");\n  }\n\n  const validation = validateYaml(\"services.yaml\", readConfig(\"services.yaml\"));\n  if (!validation.valid) {\n    return {\n      isError: true,\n      ...textContent(JSON.stringify(validation, null, 2)),\n    };\n  }\n\n  const services = parseYamlConfig(\"services.yaml\");\n  if (!Array.isArray(services)) {\n    throw new Error(\"services.yaml must contain a top-level array\");\n  }\n\n  const groupName = args.group.trim();\n  const serviceName = args.name.trim();\n  const serviceConfig = args.service ?? {};\n  assertPlainObject(serviceConfig, \"service\");\n\n  let group = services.find((entry) => isPlainObject(entry) && Object.keys(entry)[0] === groupName);\n  if (!group) {\n    group = { [groupName]: [] };\n    services.push(group);\n  }\n\n  if (!Array.isArray(group[groupName])) {\n    throw new Error(`Group '${groupName}' must contain an array`);\n  }\n\n  if (group[groupName].some((entry) => isPlainObject(entry) && Object.keys(entry)[0] === serviceName)) {","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/gethomepage/homepage/blob/b6dca1ae033e613d8e692f9a161a3cc53a5a2857/src/utils/mcp/homepage-mcp.js#L178-L214","documentation":"Thrown by addService after validation passes when services.yaml is parsed and its top-level structure is not a YAML sequence. Homepage's services config is fundamentally a list of group objects, so any other root shape is treated as corruption.","triggerScenarios":"services.yaml exists, validates (or is empty enough to pass), and parseYamlConfig returns something whose `Array.isArray(...)` is false — e.g. a YAML mapping at the root, a scalar, or null when the file is non-empty but not a list.","commonSituations":"User hand-edited services.yaml into a mapping (e.g. `Infra: [...]` at the top level instead of `- Infra: [...]`); file contains only a comment then a non-list value; an external tool rewrote it as an object; partial/failed manual edit left invalid YAML that js-yaml still parsed as null/scalar.","solutions":["Rewrite services.yaml so the root is a YAML sequence: a list whose entries are single-key group maps.","Back up the current file, then start from the documented skeleton: `- GroupName: []`.","Run validate_config_file on services.yaml to surface structural issues before re-running add_service.","If the file should be empty, make it literally empty (or `[]`) rather than `{}`."],"exampleFix":"# before (services.yaml)\nInfra:\n  - Grafana: { href: http://grafana:3000 }\n\n# after\n- Infra:\n    - Grafana:\n        href: http://grafana:3000","handlingStrategy":"validation","validationCode":"function ensureServicesArray(parsed) {\n  if (!Array.isArray(parsed)) {\n    throw new Error('services.yaml root must be a YAML sequence; refusing to proceed');\n  }\n  return parsed;\n}","typeGuard":"function isServicesList(parsed) {\n  return Array.isArray(parsed) && parsed.every((e) => e != null && typeof e === 'object' && !Array.isArray(e));\n}","tryCatchPattern":null,"preventionTips":["Seed services.yaml from the documented template before enabling MCP writes.","Run validate_config_file after manual edits.","Back up the file before programmatic mutations.","Treat any non-list root as corruption — do not auto-repair."],"tags":["mcp","services","yaml","configuration","validation"],"backgroundTag":null,"analyzedSha":"b6dca1ae033e613d8e692f9a161a3cc53a5a2857","analyzedAt":"2026-08-13T04:48:44.121Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}