abhigyanpatwari/GitNexus · error

links[ ].contract is required

Error message

links[${i}].contract is required

What it means

Thrown while validating a group.yaml links entry: the link's `contract` field is undefined, null, or trims to an empty string. Every link must point at a concrete contract identifier (the shared interface both sides are matched against), so a blank value cannot be defaulted or guessed.

Solutions

  1. Add a non-empty contract identifier to the link — the path/name of the shared API, proto, topic, or library both repos use.
  2. Match the convention of existing links in the file (e.g. services/api/openapi.yaml or the exported symbol/package name).
  3. If the link has no meaningful contract, remove the whole link entry instead of leaving contract blank.

Example fix

# before (group.yaml)
links:
  - from: services/web
    to: services/api
    type: http
    role: consumer
# -> Error: links[0].contract is required

# after
links:
  - from: services/web
    to: services/api
    type: http
    role: consumer
    contract: services/api/openapi.yaml
Defensive patterns

Strategy: validation

Validate before calling

import yaml from 'js-yaml';

export function validateLinkContracts(content: string): void {
  const doc = yaml.load(content, { schema: yaml.JSON_SCHEMA }) as Record<string, any>;
  (doc.links ?? []).forEach((link: any, i: number) => {
    if (link?.contract === undefined || link?.contract === null || String(link.contract).trim() === '') {
      throw new Error(
        `links[${i}].contract is required — point it at the shared API/proto/topic/package both repos use`,
      );
    }
  });
}

Type guard

export const hasContract = (link: unknown): link is { contract: string } =>
  !!link && typeof link === 'object' &&
  typeof String((link as any).contract?.trim?.() ?? '') === 'string' &&
  String((link as any).contract ?? '').trim().length > 0;

Try / catch

try {
  config = await loadGroupConfig(groupDir);
} catch (err) {
  if (err instanceof Error && /links\[\d+\]\.contract is required/.test(err.message)) {
    throw new Error(
      'every link needs a non-empty contract identifier (e.g. services/api/openapi.yaml) — or drop the link',
      { cause: err },
    );
  }
  throw err;
}

Prevention

When it happens

Trigger: A links[i] entry that has from/to/type/role but no contract key; contract: '' or contract: null; a contract value consisting only of whitespace.

Common situations: Filling in the easy fields first and forgetting the identifier; commenting out a contract line while debugging but leaving the link; whitespace introduced by YAML multi-line block scalars.

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-08-20). Data as JSON: /api/errors/c193a177a6557bd0. Report an issue: GitHub.

Appendix: source

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

      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`);
    }
    return {
      from: link.from as string,
      to: link.to as string,
      type: link.type as ManifestContractType,
      contract: String(link.contract),
      role: link.role as ContractRole,
    };
  });

  const rawDetect = raw.detect;
  if (
    rawDetect !== undefined &&
    (!rawDetect || typeof rawDetect !== 'object' || Array.isArray(rawDetect))
  ) {
    throw new Error('detect must be a mapping of boolean flags');
  }
  for (const [key, value] of Object.entries((rawDetect as Record<string, unknown>) || {})) {

View on GitHub (pinned to 52924ef12c)