abhigyanpatwari/GitNexus · error · GitNexusRcError
${GITNEXUS_RC_FILENAME}: "${prev}" and "${key}" both configu
Error message
${GITNEXUS_RC_FILENAME}: "${prev}" and "${key}" both configure the same option; set only one. What it means
Two keys that alias the same AnalyzeOptions target appear at the same config level. Aliases are: `defaultBranch`/`branch`; `skipAgentsMd`/`skipContextFiles`/`skipAiContext`; `stats`/`noStats`. GitNexus rejects the ambiguity rather than guessing which value wins. Note this is per-level: a flat key plus a nested `analyze` key for the same option is allowed (nested wins).
Source
Thrown at gitnexus/src/cli/analyze-config.ts:354
): Partial<AnalyzeOptions> => {
const out: Partial<AnalyzeOptions> = {};
const setBy = new Map<keyof AnalyzeOptions, string>();
for (const [key, value] of Object.entries(obj)) {
if (allowNestedKey && key === NESTED_KEY) continue; // handled separately
// `Object.hasOwn`, not a truthiness check: a plain-object lookup like
// `KEY_SPECS["__proto__"]` returns an inherited member (Object.prototype,
// truthy) and would slip past `if (!spec)`, hitting the wrong error branch
// instead of the documented "Unknown key" message (#1996 tri-review P3).
if (!Object.hasOwn(KEY_SPECS, key)) {
throw new GitNexusRcError(
`Unknown key "${key}" in ${GITNEXUS_RC_FILENAME}. ${ALLOWED_KEYS_HINT}`,
);
}
const spec = KEY_SPECS[key];
const prev = setBy.get(spec.target);
if (prev && prev !== key) {
throw new GitNexusRcError(
`${GITNEXUS_RC_FILENAME}: "${prev}" and "${key}" both configure the same option; set only one.`,
);
}
setBy.set(spec.target, key);
(out as Record<string, unknown>)[spec.target] = normalizeValue(spec.kind, value, key);
}
return out;
};
/**
* Locate, read, parse, validate, and normalize `.gitnexusrc` at `repoRoot`.
*
* @returns the normalized config defaults, or `undefined` when no file exists
* (the normal case). Throws {@link GitNexusRcError} on any problem.
*/
export function loadAnalyzeConfig(repoRoot: string): Partial<AnalyzeOptions> | undefined {
const filePath = path.join(repoRoot, GITNEXUS_RC_FILENAME);View on GitHub (pinned to d540b00184)
Solutions
- Keep only one alias per option at each level.
- Prefer the canonical name (defaultBranch, skipAgentsMd, stats).
- If you need precedence between two spellings, use the nested `analyze` block for the override.
Example fix
// before
{"branch": "main", "defaultBranch": "develop"}
// after
{"defaultBranch": "develop"} Defensive patterns
Strategy: validation
Validate before calling
const ALIAS_GROUPS = [
['defaultBranch','branch'],
['skipAgentsMd','skipContextFiles','skipAiContext'],
['stats','noStats'],
];
for (const group of ALIAS_GROUPS) {
const present = group.filter((k) => k in cfg);
if (present.length > 1) throw new Error('Set only one of: ' + present.join(', '));
} Prevention
- Prefer canonical names: defaultBranch, skipAgentsMd, stats.
- Use the nested `analyze` block (not a second alias) when you need precedence.
When it happens
Trigger: Setting { "defaultBranch": "develop", "branch": "main" } or { "skipContextFiles": true, "skipAiContext": false } at the same level.
Common situations: Merging config examples from different documentation eras (legacy `branch` + new `defaultBranch`); both alias spellings present after a refactor; copy-paste accumulation.
Related errors
- ${source} entry "${trimmed}" must be an identifier or member
- ${source} must list at least one string.
- ${source} must be a finite number.
- ${source} must be a number or numeric string.
- ${source} must be true/false or a non-negative integer (node
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/dbe91a6b37378d11.
Report an issue: GitHub.