iOfficeAI/OfficeCLI · error · ArgumentException
A sheet named '{caseMatch.Name}' already exists. Sheet names
Error message
A sheet named '{caseMatch.Name}' already exists. Sheet names must be unique. What it means
Thrown by ExcelHandler.AddSheet when a sheet with the requested name already exists (case-insensitive match) and none of the exception conditions apply (ifExists=use is not set, the existing sheet is not the pristine placeholder, or no claimable property was supplied). Excel requires sheet names to be unique (case-insensitive), so adding a second sheet with the same name is a hard error. The guard distinguishes a genuine duplicate-name collision from a legitimate placeholder-claim flow.
Source
Thrown at src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs:72
// user-created sheet (collision is a genuine error). The
// placeholder is identifiable as: workbook holds exactly one
// sheet, that sheet's worksheet has empty SheetData, no
// sheetView properties beyond defaults, no tabColor — i.e.
// a fresh `Create blank → first Add` flow.
var caseExact = string.Equals(caseMatch.Name, name, StringComparison.Ordinal);
var isPlaceholder = sheets.Elements<Sheet>().Count() == 1
&& IsPristineWorksheet(workbookPart, caseMatch);
// Placeholder claim is only meaningful when the caller actually
// supplies a sheet-level prop that would mutate the placeholder
// (autoFilter / tabColor / hidden). Without any such prop the
// "claim" is a true no-op and indistinguishable from a duplicate-
// name collision — reject so callers don't see fake success.
var hasClaimableProp = properties.ContainsKey("autoFilter")
|| properties.ContainsKey("tabColor")
|| properties.ContainsKey("hidden");
if (!caseExact || !isPlaceholder || !hasClaimableProp)
{
throw new ArgumentException(
$"A sheet named '{caseMatch.Name}' already exists. Sheet names must be unique.");
}
// Placeholder claim: route any supplied autoFilter / tabColor /
// hidden through Set so the user's intent applies — the previous
// silent no-op branch dropped them, which is what motivated
// rejecting duplicates outright.
var existingPart = (WorksheetPart)workbookPart.GetPartById(caseMatch.Id!);
var sheetMerged = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (properties.TryGetValue("autoFilter", out var dupAf)) sheetMerged["autofilter"] = dupAf;
if (properties.TryGetValue("tabColor", out var dupTc)) sheetMerged["tabcolor"] = dupTc;
if (sheetMerged.Count > 0)
SetSheetLevel(existingPart, name, sheetMerged);
if (properties.TryGetValue("hidden", out var dupHidden) && ParseHelpers.IsTruthy(dupHidden))
caseMatch.State = SheetStateValues.Hidden;
return $"/sheet[@name='{name}']";
}
var newWorksheetPart = workbookPart.AddNewPart<WorksheetPart>();View on GitHub (pinned to 1ced45e900)
Solutions
- Set ifExists=use in the properties to claim the existing sheet as a no-op success (this is what single-sheet subtree dumps emit for replay compatibility).
- Choose a unique sheet name.
- Delete or rename the existing sheet first before adding the new one.
- If replaying a dump onto a workbook that already has the sheet, ensure the batch items include ifExists=use on the 'add sheet' command.
Example fix
// before: adding a sheet whose name already exists
handler.Add("/", "sheet", new { name = "Sheet1" }); // throws if Sheet1 exists
// after: claim the existing sheet as a no-op
handler.Add("/", "sheet", new { name = "Sheet1", ifExists = "use" }); Defensive patterns
Strategy: validation
Validate before calling
// Check for existing sheet name before adding
var existing = handler.GetSheetNames();
if (existing.Any(s => s.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
// Use ifExists=use to claim the existing sheet, or pick a different name
handler.Add("/", "sheet", new { name, ifExists = "use" });
}
else
{
handler.Add("/", "sheet", new { name });
} Try / catch
try
{
handler.Add("/", "sheet", new { name = sheetName });
}
catch (ArgumentException ex) when (ex.Message.Contains("already exists"))
{
// Sheet exists — claim it as a no-op for replay-safe batch operations
handler.Add("/", "sheet", new { name = sheetName, ifExists = "use" });
} Prevention
- Use ifExists=use when replaying dumps onto workbooks that may already contain the target sheet.
- Check existing sheet names before adding to choose a unique name.
- In batch operations, always include ifExists=use on add-sheet commands for replay safety.
- Remember Excel sheet names are case-insensitive — 'Data' and 'DATA' collide.
When it happens
Trigger: Calling Add with type=sheet and a name that matches an existing sheet, without setting ifExists=use and without targeting the BlankDocCreator placeholder. For example: workbook already has 'Sheet1' and you Add another sheet named 'Sheet1'. Or: Add with name='Data' when 'Data' already exists with content.
Common situations: A batch replay that creates sheets which already exist in the target workbook (common when dumping from one workbook and replaying onto another that already has those sheets); a script that auto-generates sheet names without checking for collisions; a user adding a sheet whose name differs only in case from an existing one ('Data' vs 'DATA').
Related errors
- invalid_value
- unsupported_type
- number format is {formatCode.Length} chars; Excel's limit is
- number format has unbalanced square brackets: '{formatCode}'
- Invalid font size: '{szVal}'. Excel font size must be <= 409
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/7f503a6497c6fb26.
Report an issue: GitHub.