abhigyanpatwari/GitNexus · error

repos is required in group.yaml (must be a mapping)

Error message

repos is required in group.yaml (must be a mapping)

What it means

Thrown by parseGroupConfig() when the group.yaml object has no `repos` key, or when repos is not a mapping — an array or scalar both fail (Array.isArray check plus typeof 'object'). repos must map repo paths to their git URLs/locations; it defines both group membership and the valid path keys that links may reference.

Solutions

  1. Convert the list to a mapping keyed by repo path with the URL as value.
  2. Use stable relative paths as keys (these same strings must be used by links[].from/to).
  3. Keep at least one entry: a group with zero repos still needs the mapping form.

Example fix

# before (group.yaml)
version: 1
name: my-group
repos:
  - https://github.com/org/api
  - https://github.com/org/web
# -> Error: repos is required in group.yaml (must be a mapping)

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

Strategy: validation

Validate before calling

import yaml from 'js-yaml';

export function requireReposMapping(content: string): void {
  const doc = yaml.load(content, { schema: yaml.JSON_SCHEMA }) as Record<string, unknown>;
  const repos = doc.repos;
  if (!repos || typeof repos !== 'object' || Array.isArray(repos)) {
    throw new Error('group.yaml: `repos` must be a mapping of { repo-path: url }, not a list');
  }
}

Type guard

export const isReposMapping = (v: unknown): v is Record<string, string> =>
  !!v && typeof v === 'object' && !Array.isArray(v) &&
  Object.values(v).every((u) => typeof u === 'string');

Try / catch

try {
  config = await loadGroupConfig(groupDir);
} catch (err) {
  if (err instanceof Error && err.message === 'repos is required in group.yaml (must be a mapping)') {
    throw new Error(
      'group.yaml repos must be key/value pairs:\n  repos:\n    services/api: https://github.com/org/api',
      { cause: err },
    );
  }
  throw err;
}

Prevention

When it happens

Trigger: Loading a group.yaml where repos is a YAML list of URLs (repos:\n - https://...), a single string, or absent entirely.

Common situations: Porting a repos list from another monorepo tool (GitHub org list, meta repo config) that uses arrays; writing `repos: github.com/org` as a bare string.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

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

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}`,
      );
    }
    repos[memberPath] = registryName.trim();
  }
  const repoPaths = new Set(Object.keys(repos));

  const rawLinks = (raw.links as unknown[]) || [];
  const links: GroupManifestLink[] = rawLinks.map((l: unknown, i: number) => {
    const link = l as Record<string, unknown>;
    if (!link.from || !repoPaths.has(link.from as string)) {

View on GitHub (pinned to 52924ef12c)