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
- Add a non-empty contract identifier to the link — the path/name of the shared API, proto, topic, or library both repos use.
- Match the convention of existing links in the file (e.g. services/api/openapi.yaml or the exported symbol/package name).
- 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
- Fill contract at the same time as type/role — a link without a contract has no matching meaning.
- Adopt one convention for contract ids (proto path, openapi path, topic name, package name) and stick to it.
- If a link is speculative, comment out the entire entry; a blank contract fails validation.
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
- links[ ].from " " does not match any repo path in group
- links[ ].role " " is invalid. Expected: provider | consumer
- links[ ].to " " does not match any repo path in group
- links[ ].type " " is invalid. Expected
- name is required in group.yaml
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)