abhigyanpatwari/GitNexus · error · Error

Invalid YAML: expected an object

Error message

Invalid YAML: expected an object

What it means

Thrown by parseGroupConfig() when yaml.load() of the group.yaml content produces anything other than a plain mapping — null (empty document), a scalar, or an array. The loader uses the JSON_SCHEMA yaml schema, so the document must be a JSON-like object at the top level. It fires before any field validation, meaning the file's overall shape is wrong.

Solutions

  1. Make the document a top-level mapping with the required keys: version, name, repos (plus optional links/detect/matching).
  2. If the file looks empty, restore it from version control or re-create it from the documented group.yaml template.
  3. Validate locally before syncing: node -e "const y=require('js-yaml');const d=y.load(require('fs').readFileSync('group.yaml','utf8'),{schema:y.JSON_SCHEMA});console.log(typeof d, d&&typeof d==='object'&&!Array.isArray(d))".

Example fix

# before (group.yaml)
- repos:
    services/api: https://github.com/org/api
# -> Error: Invalid YAML: expected an object

# after
version: 1
name: my-group
repos:
  services/api: https://github.com/org/api
Defensive patterns

Strategy: validation

Validate before calling

// Validate shape BEFORE loadGroupConfig so users get your message, not a raw throw.
import yaml from 'js-yaml';

export function prevalidateGroupYaml(content: string): void {
  const doc = yaml.load(content, { schema: yaml.JSON_SCHEMA });
  if (!doc || typeof doc !== 'object' || Array.isArray(doc)) {
    throw new Error(
      'group.yaml must be a mapping starting with keys like version/name/repos — ' +
        'found ' + (doc === null ? 'an empty document' : typeof doc),
    );
  }
}

Type guard

import yaml from 'js-yaml';

export const isGroupYamlObject = (content: string): boolean => {
  try {
    const doc = yaml.load(content, { schema: yaml.JSON_SCHEMA });
    return !!doc && typeof doc === 'object' && !Array.isArray(doc);
  } catch {
    return false;
  }
};

Try / catch

try {
  config = await loadGroupConfig(groupDir);
} catch (err) {
  if (err instanceof Error && err.message === 'Invalid YAML: expected an object') {
    throw new Error(
      `${groupDir}/group.yaml is not a YAML mapping. Start from:
version: 1
name: my-group
repos:
  path/to/repo: <url>`,
      { cause: err },
    );
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling parseGroupConfig()/loadGroupConfig() on a group.yaml that is empty, contains only a comment, holds a bare scalar (e.g. just a group name), or whose top level is a YAML list (e.g. a repos list without the surrounding keys).

Common situations: Starting a group.yaml from a snippet that was only the repos block; truncating the file during a merge conflict resolution; pasting a markdown code fence into the file so the first token is a scalar.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@52924ef12c (2026-08-20). Data as JSON: /api/errors/c3413814a3b9636d. Report an issue: GitHub.

Appendix: source

Thrown at gitnexus/src/core/group/config-parser.ts:51

  http: true,
  graphql: false,
  grpc: true,
  thrift: true,
  topics: true,
  includes: false,
  workspace_deps: false,
};

const DEFAULT_MATCHING = {
  exclude_links_paths: [] as string[],
  exclude_links_param_only_paths: false,
};

export function parseGroupConfig(yamlContent: string): GroupConfig {
  const raw = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA }) as Record<string, unknown>;

  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
    throw new Error('Invalid YAML: expected an object');
  }

  if (raw.version === undefined) throw new Error('version is required in group.yaml');
  if (raw.version !== 1) {
    throw new Error(`Unsupported group.yaml version: ${raw.version}. Expected 1.`);
  }
  if (!raw.name || typeof raw.name !== 'string') throw new Error('name is required in group.yaml');
  if (!raw.repos || typeof raw.repos !== 'object' || Array.isArray(raw.repos)) {
    throw new Error('repos is required in group.yaml (must be a mapping)');
  }

  const reposRaw = raw.repos as Record<string, unknown>;
  const repos: Record<string, string> = {};
  for (const [memberPath, registryName] of Object.entries(reposRaw)) {
    if (typeof registryName !== 'string' || registryName.trim() === '') {
      throw new Error(
        `repos["${memberPath}"] must be a non-empty registry name string, not ${typeof registryName}`,
      );

View on GitHub (pinned to 52924ef12c)