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

  1. Keep only one alias per option at each level.
  2. Prefer the canonical name (defaultBranch, skipAgentsMd, stats).
  3. 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

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


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/dbe91a6b37378d11. Report an issue: GitHub.