abhigyanpatwari/GitNexus · error · Error

links[ ].from " " does not match any repo path in group

Error message

links[${i}].from "${link.from}" does not match any repo path in group

What it means

Thrown while validating each entry of group.yaml's optional `links` list: the link's `from` value is missing or is not one of the keys of the `repos` mapping. Links may only connect repos declared in the same manifest, so an unknown or misspelled path is rejected before the link graph is built.

Solutions

  1. Copy the exact key from the repos mapping into links[i].from (the error echoes the offending value for comparison).
  2. If the repo was renamed, update every links[].from/to that referenced the old key, not just repos.
  3. Use one path convention (plain relative keys, no ./ prefix, no trailing slash) throughout the file.

Example fix

# before (group.yaml)
repos:
  services/api: https://github.com/org/api
links:
  - from: ./services/api
    to: services/web
# -> Error: links[0].from "./services/api" does not match any repo path in group

# after
links:
  - from: services/api
    to: services/web
Defensive patterns

Strategy: validation

Validate before calling

import yaml from 'js-yaml';

// Validate link endpoints against repos keys BEFORE loadGroupConfig.
export function validateLinkRefs(content: string): void {
  const doc = yaml.load(content, { schema: yaml.JSON_SCHEMA }) as Record<string, any>;
  const repoPaths = new Set(Object.keys(doc.repos ?? {}));
  (doc.links ?? []).forEach((link: any, i: number) => {
    for (const side of ['from', 'to'] as const) {
      if (!repoPaths.has(link?.[side])) {
        throw new Error(
          `links[${i}].${side} ${JSON.stringify(link?.[side])} is not a repos key ` +
            `(known: ${[...repoPaths].join(', ')})`,
        );
      }
    }
  });
}

Type guard

export const isKnownRepoPath = (p: unknown, repoPaths: Set<string>): p is string =>
  typeof p === 'string' && repoPaths.has(p);

Try / catch

try {
  config = await loadGroupConfig(groupDir);
} catch (err) {
  const m = /^links\[(\d+)\]\.(from|to) (.*) does not match/.exec(
    err instanceof Error ? err.message : '',
  );
  if (m) {
    throw new Error(
      `group.yaml links[${m[1]}].${m[2]}: copy the exact repos key (check typo, ./ prefix, trailing slash)`,
      { cause: err },
    );
  }
  throw err;
}

Prevention

When it happens

Trigger: A links[i].from value that differs from a repos key: typo (servcies/api vs services/api), absolute vs relative path style, a trailing slash or ./ prefix mismatch, or a repo that was removed from repos while its link remained.

Common situations: Renaming a repo path in repos without updating the links that reference it; copying a link block between group.yaml files of different groups; mixing path conventions (./services/api vs services/api).

Related errors


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

Appendix: source

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

  }

  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)) {
      throw new Error(`links[${i}].from "${link.from}" does not match any repo path in group`);
    }
    if (!link.to || !repoPaths.has(link.to as string)) {
      throw new Error(`links[${i}].to "${link.to}" does not match any repo path in group`);
    }
    if (!VALID_CONTRACT_TYPES.includes(link.type as ManifestContractType)) {
      throw new Error(
        `links[${i}].type "${link.type}" is invalid. Expected: ${VALID_CONTRACT_TYPES.join(', ')}`,
      );
    }
    if (!VALID_ROLES.includes(link.role as ContractRole)) {
      throw new Error(`links[${i}].role "${link.role}" is invalid. Expected: provider | consumer`);
    }
    if (
      link.contract === undefined ||
      link.contract === null ||
      String(link.contract).trim() === ''
    ) {
      throw new Error(`links[${i}].contract is required`);

View on GitHub (pinned to 52924ef12c)