{"record":{"id":"35cdf36a51b5fac6","repo":"abhigyanpatwari/GitNexus","slug":"links-i-from-link-from-does-not-match-any","errorCode":null,"errorMessage":"links[${i}].from \"${link.from}\" does not match any repo path in group","messagePattern":"links\\[(.+?)\\]\\.from \"(.+?)\" does not match any repo path in group","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/group/config-parser.ts","lineNumber":79,"sourceCode":"  }\n\n  const reposRaw = raw.repos as Record<string, unknown>;\n  const repos: Record<string, string> = {};\n  for (const [memberPath, registryName] of Object.entries(reposRaw)) {\n    if (typeof registryName !== 'string' || registryName.trim() === '') {\n      throw new Error(\n        `repos[\"${memberPath}\"] must be a non-empty registry name string, not ${typeof registryName}`,\n      );\n    }\n    repos[memberPath] = registryName.trim();\n  }\n  const repoPaths = new Set(Object.keys(repos));\n\n  const rawLinks = (raw.links as unknown[]) || [];\n  const links: GroupManifestLink[] = rawLinks.map((l: unknown, i: number) => {\n    const link = l as Record<string, unknown>;\n    if (!link.from || !repoPaths.has(link.from as string)) {\n      throw new Error(`links[${i}].from \"${link.from}\" does not match any repo path in group`);\n    }\n    if (!link.to || !repoPaths.has(link.to as string)) {\n      throw new Error(`links[${i}].to \"${link.to}\" does not match any repo path in group`);\n    }\n    if (!VALID_CONTRACT_TYPES.includes(link.type as ManifestContractType)) {\n      throw new Error(\n        `links[${i}].type \"${link.type}\" is invalid. Expected: ${VALID_CONTRACT_TYPES.join(', ')}`,\n      );\n    }\n    if (!VALID_ROLES.includes(link.role as ContractRole)) {\n      throw new Error(`links[${i}].role \"${link.role}\" is invalid. Expected: provider | consumer`);\n    }\n    if (\n      link.contract === undefined ||\n      link.contract === null ||\n      String(link.contract).trim() === ''\n    ) {\n      throw new Error(`links[${i}].contract is required`);","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/52924ef12c2290ceee4612526a828ec4cdf2047f/gitnexus/src/core/group/config-parser.ts#L61-L97","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Copy the exact key from the repos mapping into links[i].from (the error echoes the offending value for comparison).","If the repo was renamed, update every links[].from/to that referenced the old key, not just repos.","Use one path convention (plain relative keys, no ./ prefix, no trailing slash) throughout the file."],"exampleFix":"# before (group.yaml)\nrepos:\n  services/api: https://github.com/org/api\nlinks:\n  - from: ./services/api\n    to: services/web\n# -> Error: links[0].from \"./services/api\" does not match any repo path in group\n\n# after\nlinks:\n  - from: services/api\n    to: services/web","handlingStrategy":"validation","validationCode":"import yaml from 'js-yaml';\n\n// Validate link endpoints against repos keys BEFORE loadGroupConfig.\nexport function validateLinkRefs(content: string): void {\n  const doc = yaml.load(content, { schema: yaml.JSON_SCHEMA }) as Record<string, any>;\n  const repoPaths = new Set(Object.keys(doc.repos ?? {}));\n  (doc.links ?? []).forEach((link: any, i: number) => {\n    for (const side of ['from', 'to'] as const) {\n      if (!repoPaths.has(link?.[side])) {\n        throw new Error(\n          `links[${i}].${side} ${JSON.stringify(link?.[side])} is not a repos key ` +\n            `(known: ${[...repoPaths].join(', ')})`,\n        );\n      }\n    }\n  });\n}","typeGuard":"export const isKnownRepoPath = (p: unknown, repoPaths: Set<string>): p is string =>\n  typeof p === 'string' && repoPaths.has(p);","tryCatchPattern":"try {\n  config = await loadGroupConfig(groupDir);\n} catch (err) {\n  const m = /^links\\[(\\d+)\\]\\.(from|to) (.*) does not match/.exec(\n    err instanceof Error ? err.message : '',\n  );\n  if (m) {\n    throw new Error(\n      `group.yaml links[${m[1]}].${m[2]}: copy the exact repos key (check typo, ./ prefix, trailing slash)`,\n      { cause: err },\n    );\n  }\n  throw err;\n}","preventionTips":["Copy-paste repo keys from the repos block instead of retyping them in links.","Adopt one path style (no ./ prefix, no trailing slash) and enforce it in review.","When renaming a repos key, grep the file for the old value — every links from/to referencing it must be updated."],"tags":["yaml","configuration","validation","group","links","reference-error"],"backgroundTag":"unresolved-config-reference","analyzedSha":"52924ef12c2290ceee4612526a828ec4cdf2047f","analyzedAt":"2026-08-20T23:29:22.980Z","contentChangedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}