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
- Replace the value with one of the enumerated types exactly as printed in the message: http, grpc, thrift, topic, lib, custom, include.
- 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).
- 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
- Keep the enum in mind when authoring: REST/HTTP APIs -> http; message brokers -> topic; shared libraries -> lib; C/C++ headers -> include.
- Use a YAML schema/linter with the seven allowed values for links[].type.
- When no builtin fits, use custom — never invent a new token.
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
- links[ ].role " " is invalid. Expected: provider | consumer
- links[ ].contract is required
- links[ ].from " " does not match any repo path in group
- links[ ].to " " does not match any repo path in group
- Invalid YAML: expected an object
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)