gethomepage/homepage · error · Error
services.yaml must contain a top-level array
Error message
services.yaml must contain a top-level array
What it means
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.
Source
Thrown at src/utils/mcp/homepage-mcp.js:196
if (typeof args.group !== "string" || !args.group.trim()) {
throw new Error("group must be a non-empty string");
}
if (typeof args.name !== "string" || !args.name.trim()) {
throw new Error("name must be a non-empty string");
}
const validation = validateYaml("services.yaml", readConfig("services.yaml"));
if (!validation.valid) {
return {
isError: true,
...textContent(JSON.stringify(validation, null, 2)),
};
}
const services = parseYamlConfig("services.yaml");
if (!Array.isArray(services)) {
throw new Error("services.yaml must contain a top-level array");
}
const groupName = args.group.trim();
const serviceName = args.name.trim();
const serviceConfig = args.service ?? {};
assertPlainObject(serviceConfig, "service");
let group = services.find((entry) => isPlainObject(entry) && Object.keys(entry)[0] === groupName);
if (!group) {
group = { [groupName]: [] };
services.push(group);
}
if (!Array.isArray(group[groupName])) {
throw new Error(`Group '${groupName}' must contain an array`);
}
if (group[groupName].some((entry) => isPlainObject(entry) && Object.keys(entry)[0] === serviceName)) {View on GitHub (pinned to b6dca1ae03)
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 `{}`.
Example fix
# before (services.yaml)
Infra:
- Grafana: { href: http://grafana:3000 }
# after
- Infra:
- Grafana:
href: http://grafana:3000 Defensive patterns
Strategy: validation
Validate before calling
function ensureServicesArray(parsed) {
if (!Array.isArray(parsed)) {
throw new Error('services.yaml root must be a YAML sequence; refusing to proceed');
}
return parsed;
} Type guard
function isServicesList(parsed) {
return Array.isArray(parsed) && parsed.every((e) => e != null && typeof e === 'object' && !Array.isArray(e));
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Group '${groupName}' must contain an array
- group must be a non-empty string
- name must be a non-empty string
- widgets.yaml must contain a top-level array
- Unsupported config file '${file}'. Supported files: ${CONFIG
AI-assisted analysis of gethomepage/homepage@b6dca1ae03 (2026-08-13).
Data as JSON: /api/errors/39b4133df7cb21d4.
Report an issue: GitHub.