iOfficeAI/OfficeCLI · error · ArgumentException

Defined name '{nrName}' already exists in workbook scope; re

Error message

Defined name '{nrName}' already exists in workbook scope; remove it before adding a new one or pick a different name.

What it means

Thrown when a defined name with the same identifier AND the same scope (LocalSheetId) already exists in the workbook. OOXML permits the same name across different scopes (workbook-global vs a specific sheet, or two distinct sheets), but a duplicate (name, localSheetId) pair triggers Excel's 'found a problem' repair dialog. The check iterates existing <definedName> entries comparing name (case-insensitive) and LocalSheetId, and additionally checks for collisions with ListObject table names. The message states whether the conflict is sheet-scoped or workbook-scoped.

Source

Thrown at src/officecli/Handlers/Excel/ExcelHandler.Add.Tables.cs:191

        // recalc engine treats function-flagged defined names as volatile,
        // forcing recalc on every workbook change.
        if (properties.TryGetValue("volatile", out var nrVolatile) && IsTruthy(nrVolatile))
            dn.Function = true;

        // CONSISTENCY(definedname-unique): Excel rejects two
        // <definedName> entries that share both name AND scope
        // (LocalSheetId) with a "found a problem" repair dialog.
        // Same name across different scopes (workbook-global vs
        // per-sheet, or two distinct sheets) is legal — only the
        // (name, localSheetId) pair must be unique.
        var dnLocalId = dn.LocalSheetId?.Value;
        foreach (var existingDn in definedNames.Elements<DefinedName>())
        {
            var existingName = existingDn.Name?.Value;
            if (existingName == null) continue;
            if (!string.Equals(existingName, nrName, StringComparison.OrdinalIgnoreCase)) continue;
            if (existingDn.LocalSheetId?.Value == dnLocalId)
                throw new ArgumentException(
                    $"Defined name '{nrName}' already exists" +
                    (dnLocalId.HasValue ? $" in sheet scope (localSheetId={dnLocalId})" : " in workbook scope") +
                    "; remove it before adding a new one or pick a different name.");
        }

        // Mirror of the table-side check: Excel's name namespace spans
        // defined names AND ListObject table names; a collision passes
        // schema validation but real Excel refuses the file (0x800A03EC).
        foreach (var existingTable in _doc.WorkbookPart!.WorksheetParts
            .SelectMany(wp => wp.TableDefinitionParts)
            .Select(tdp => tdp.Table)
            .Where(t => t != null)!)
        {
            if (string.Equals(existingTable!.Name?.Value, nrName, StringComparison.OrdinalIgnoreCase)
                || string.Equals(existingTable.DisplayName?.Value, nrName, StringComparison.OrdinalIgnoreCase))
                throw new ArgumentException(
                    $"Defined name '{nrName}' collides with the table name '{existingTable.Name?.Value ?? existingTable.DisplayName?.Value}'. Excel requires table and defined names to be unique in one namespace; pick a different name.");
        }

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Remove the existing defined name first (via the tool's remove/delete command) then re-add.
  2. Pick a different name for the new defined range.
  3. If you want a per-sheet name with the same spelling as a workbook-global one, add it scoped to a specific sheet (different LocalSheetId) — that is legal.

Example fix

// before
add ./book.xlsx /namedrange --type namedrange --prop name=SalesTotal --prop ref=Sheet1!A1  // when SalesTotal already exists
// after
// (option A) remove then re-add, or (option B) rename
add ./book.xlsx /namedrange --type namedrange --prop name=SalesTotal2024 --prop ref=Sheet1!A1
Defensive patterns

Strategy: validation

Validate before calling

// Reject (name, scope) duplicates before add, mirroring the handler.
foreach (var dn in definedNames.Elements<DefinedName>())
{
    if (string.Equals(dn.Name?.Value, name, StringComparison.OrdinalIgnoreCase)
        && dn.LocalSheetId?.Value == targetLocalSheetId)
        throw new InvalidOperationException($"'{name}' already exists in that scope; remove or rename");
}
// Also guard against ListObject table-name collisions.

Type guard

static bool DefinedNameIsUnique(IEnumerable<DefinedName> existing, string name, uint? scope)
    => !existing.Any(d => string.Equals(d.Name?.Value, name, StringComparison.OrdinalIgnoreCase)
        && d.LocalSheetId?.Value == scope);

Try / catch

try { handler.AddNamedRange(...); }
catch (ArgumentException ex) when (ex.Message.Contains("already exists"))
{ /* remove the old name first, or pick a new name — do NOT retry unchanged */ }

Prevention

When it happens

Trigger: Calling add namedrange with a `name=` that already exists at the same scope (e.g. re-running a script that adds `SalesTotal` at workbook scope twice); or a name that collides with an existing table name. A same-named name on a DIFFERENT sheet scope does NOT trigger this.

Common situations: Re-running an import script without first removing the previously added name; merging workbooks that each defined the same global name; choosing a name that matches an existing table.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/a33e55be3f4ffdef. Report an issue: GitHub.