abhigyanpatwari/GitNexus · error

links[ ].type " " is invalid. Expected

Error message

links[${i}].type "${link.type}" is invalid. Expected: ${VALID_CONTRACT_TYPES.join(', ')}

What it means

Thrown while validating a group.yaml links entry: the link's `type` is not in the supported contract-type enum — http, grpc, thrift, topic, lib, custom, include. The message lists the full accepted set. Type decides which contract extractor handles the link during group sync, so unknown values cannot be defaulted.

Solutions

  1. Replace the value with one of the enumerated types exactly as printed in the message: http, grpc, thrift, topic, lib, custom, include.
  2. For REST/HTTP APIs use http; for message brokers use topic; for shared code dependencies use lib; for C/C++ header tracking use include (requires detect.includes: true).
  3. If none fit, use custom rather than inventing a new token.

Example fix

# before (group.yaml)
links:
  - from: services/web
    to: services/api
    type: rest
# -> Error: links[0].type "rest" is invalid. Expected: http, grpc, thrift, topic, lib, custom, include

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

Strategy: validation

Validate before calling

import yaml from 'js-yaml';

const VALID_CONTRACT_TYPES = ['http', 'grpc', 'thrift', 'topic', 'lib', 'custom', 'include'];

export function validateLinkTypes(content: string): void {
  const doc = yaml.load(content, { schema: yaml.JSON_SCHEMA }) as Record<string, any>;
  (doc.links ?? []).forEach((link: any, i: number) => {
    if (!VALID_CONTRACT_TYPES.includes(link?.type)) {
      throw new Error(
        `links[${i}].type must be one of ${VALID_CONTRACT_TYPES.join(', ')} — got ${JSON.stringify(link?.type)}`,
      );
    }
  });
}

Type guard

const VALID_CONTRACT_TYPES = new Set(['http', 'grpc', 'thrift', 'topic', 'lib', 'custom', 'include']);

export const isContractType = (t: unknown): t is 'http' | 'grpc' | 'thrift' | 'topic' | 'lib' | 'custom' | 'include' =>
  typeof t === 'string' && VALID_CONTRACT_TYPES.has(t);

Try / catch

try {
  config = await loadGroupConfig(groupDir);
} catch (err) {
  if (err instanceof Error && /links\[\d+\]\.type .* is invalid/.test(err.message)) {
    throw new Error(
      'link type must be one of: http, grpc, thrift, topic, lib, custom, include (REST=http, broker=topic)',
      { cause: err },
    );
  }
  throw err;
}

Prevention

When it happens

Trigger: A links[i].type that is misspelled (rest, kafka, library), uses a synonym outside the enum (message-queue for topic), is missing (undefined fails includes), or has different casing (Http — YAML under JSON_SCHEMA keeps case, and the enum is lowercase).

Common situations: Describing a REST dependency as `rest` instead of `http`; labeling a Kafka link `kafka` instead of `topic`; C/C++ header dependency written as `header` instead of the opt-in `include` type.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

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

View on GitHub (pinned to 52924ef12c)