iOfficeAI/OfficeCLI · error · ArgumentException
Cannot remove container element '{path}': it is a required s
Error message
Cannot remove container element '{path}': it is a required structural element of the document. What it means
Thrown by ExcelHandler.Remove when the path equals '/workbook' (case-insensitive, trailing slash trimmed). The workbook root is a required structural container and cannot be deleted; removing it would destroy the document. Sheet-level removal is a legitimate op with its own guard (cannot remove the last sheet) elsewhere, but /workbook is rejected up front.
Source
Thrown at src/officecli/Handlers/Excel/ExcelHandler.Remove.cs:27
using XDR = DocumentFormat.OpenXml.Drawing.Spreadsheet;
using X14 = DocumentFormat.OpenXml.Office2010.Excel;
using OfficeCli.Core;
namespace OfficeCli.Handlers;
public partial class ExcelHandler
{
public string? Remove(string path, Dictionary<string, string>? properties = null)
{
// Phase 4: trackChange.* is Word-only. Silently ignored here.
Modified = true;
// CONSISTENCY(container-remove-guard): reject removal of the
// workbook root up front. Sheet-level removal has its own guard
// (can't remove last sheet) further down and is a legitimate op;
// /workbook is not.
if (!string.IsNullOrEmpty(path)
&& path.TrimEnd('/').Equals("/workbook", StringComparison.OrdinalIgnoreCase))
throw new ArgumentException(
$"Cannot remove container element '{path}': it is a required structural element of the document.");
// Batch Remove: a selector path (not starting with '/') → Query → Remove
// each match, mirroring ExcelHandler.Set's selector branch. Row removals
// are TRUE shift-deletes (rows below shift up — see "row[N] — true shift
// delete"), so multiple matched rows MUST be removed in DESCENDING row
// order: deleting /Sheet/row[2] first renumbers the old row[4] to row[3]
// and the next delete would hit the wrong row. Non-row targets carry
// index 0 and keep a stable relative order.
if (!string.IsNullOrEmpty(path)
&& (!path.StartsWith("/") || Core.AttributeFilter.IsContentFilterPath(path)))
{
// Narrow via the shared engine (same as Set / query): pure-AND on the
// legacy path, `or` selectors queried bracket-stripped then narrowed by
// the boolean expression tree. The IsContentFilterPath arm routes a
// `/`-scoped content filter (`/Sheet1/cell[value>5 or value<1]`) here
// too, matching the Set dispatch — query, set and remove now agree on
// every selector shape.View on GitHub (pinned to 1ced45e900)
Solutions
- Target a real removable element: a sheet (remove /Sheet1), a cell, a row, or a shape.
- If you want an empty workbook, create a new one rather than removing the root.
- In removal loops, stop at the sheet level and never synthesize a '/workbook' path.
Example fix
// before remove /workbook // after remove /Sheet1 // remove a specific sheet (last-sheet guard applies)
Defensive patterns
Strategy: validation
Validate before calling
def assert_not_workbook_root(path):
norm = path.strip().rstrip('/').lower()
assert norm != '/workbook', 'cannot remove the workbook root — target a sheet or element instead'
assert_not_workbook_root('/workbook') Type guard
null
Try / catch
null
Prevention
- Never synthesize a '/workbook' removal path in loops.
- Stop removal walks at the sheet level.
- To start fresh, create a new workbook instead of removing the root.
When it happens
Trigger: remove /workbook, or remove /workbook/. Any removal request whose normalized path is exactly the workbook root.
Common situations: Programmatic removal loops that walk up to the root; misunderstanding the path hierarchy and trying to clear the whole document in one call.
Related errors
- invalid_value
- Workbook is missing
- Workbook has no sheets element
- Corrupt file: workbook missing
- not_found
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/dbfb5c76cdb91580.
Report an issue: GitHub.