{"record":{"id":"dff15356091fce84","repo":"iOfficeAI/OfficeCLI","slug":"bare-selector-rejected","errorCode":"bare_selector_rejected","errorMessage":"Bare selector '{path}' is not allowed for '{verb}' — it would match across the whole document.","messagePattern":"Bare selector '(.+?)' is not allowed for '(.+?)' — it would match across the whole document\\.","errorType":"exception","errorClass":"CliException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/MutationSelectorGuard.cs","lineNumber":49,"sourceCode":"\n    /// <summary>\n    /// Throw a CliException when <paramref name=\"path\"/> is a bare unscoped\n    /// selector on a mutating verb. No-op for `/`-scoped paths, Excel `Sheet!Ref`\n    /// notation, and null/empty (handled downstream).\n    /// </summary>\n    public static void EnsureScoped(string? path, string verb)\n    {\n        if (string.IsNullOrEmpty(path)) return;\n        if (path.StartsWith(\"/\")) return;\n        if (ExcelNotation.IsMatch(path)) return;\n        // A slash path that lost its leading slash (\"Sheet1/row[...]\") IS\n        // scoped — query already restores the slash and resolves it (see\n        // ExcelHandler.QueryDispatch); rejecting it here as \"would match\n        // across the whole document\" was both inconsistent and untrue. A '/'\n        // inside a predicate value (row[url~=a/b]) does not count.\n        if (SelectorCommaSplit.ContainsTopLevelChar(path, '/')) return;\n\n        throw new CliException(\n            $\"Bare selector '{path}' is not allowed for '{verb}' — it would match across the whole document.\")\n        {\n            Code = \"bare_selector_rejected\",\n            Suggestion =\n                $\"Scope the {verb} to a path: '/Sheet1/{path}' / '/slide[1]/{path}' / '/body/p[1]/{path}', \" +\n                \"or use Excel notation 'Sheet1!A1'. Bare selectors stay available on read-only 'query'.\",\n        };\n    }\n}\n","sourceCodeStart":31,"sourceCodeEnd":59,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/MutationSelectorGuard.cs#L31-L59","documentation":"Thrown by MutationSelectorGuard.EnsureScoped when a mutating verb (set, remove) is called with a bare, unscoped selector that would match across the entire document. A bare selector like 'cell', 'run', 'shape', or 'cell[value>5]' has no path prefix, so it could rewrite or delete every matching element in the document — one mistaken 'set cell' would change every cell. The guard requires either a '/'-scoped path (e.g. '/Sheet1/cell'), Excel notation (e.g. 'Sheet1!A1'), or a path containing a top-level '/'. The 'query' verb is intentionally NOT guarded.","triggerScenarios":"Calling 'set cell value=0' with no path scope. Calling 'remove run' on a Word document. Passing any selector string that: does not start with '/', does not match the Excel notation regex (^[^/\\[\\]]+!), and does not contain a top-level '/' character. The guard is called from the CLI/MCP/resident/batch agent-facing layer, not from the handler's internal Set/Remove API.","commonSituations":"An agent or LLM that generates a set/remove command without understanding the scoping requirement. A user who copy-pasted a query selector (which is valid for read-only query) into a set/remove command. A batch script that reuses a discovery selector for a mutation without adding a path prefix.","solutions":["Scope the selector with a '/'-prefixed path: '/Sheet1/cell[...]', '/slide[1]/shape[...]', or '/body/p[1]/run[...]'.","Use Excel notation 'Sheet1!A1' or 'Sheet1!A1:B5' for cell-range mutations.","Use 'query' instead of 'set'/'remove' if you intended a read-only operation with a bare selector.","Add the sheet, slide, or paragraph scope to the selector before the element type."],"exampleFix":"// before — bare selector rejected\nset cell value=0\nremove run\n\n// after — scoped selectors\nset /Sheet1/cell value=0\nremove /body/p[3]/run[1]\n// or Excel notation\nset Sheet1!A1 value=0","handlingStrategy":"validation","validationCode":"// Pre-check that a mutation selector is scoped before calling set/remove\nprivate static bool IsMutationSelectorScoped(string? path)\n{\n    if (string.IsNullOrEmpty(path)) return true; // null/empty handled downstream\n    if (path.StartsWith(\"/\")) return true;\n    if (System.Text.RegularExpressions.Regex.IsMatch(path, @\"^[^/\\[\\]]+!\")) return true; // Excel notation\n    if (path.Contains('/')) return true; // has a path separator\n    return false;\n}\n\nif (!IsMutationSelectorScoped(selectorPath))\n{\n    Console.Error.WriteLine($\"Bare selector '{selectorPath}' is not allowed for mutation. Scope it: '/Sheet1/{selectorPath}' or 'Sheet1!A1'\");\n    return;\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    handler.Set(path, properties);\n}\ncatch (CliException ex) when (ex.Code == \"bare_selector_rejected\")\n{\n    // ex.Suggestion contains guidance — display it and prompt user to scope the selector\n    Console.Error.WriteLine(ex.Suggestion);\n}","preventionTips":["Always prefix mutation selectors with a '/'-scoped path: '/Sheet1/cell', '/slide[1]/shape', '/body/p[1]/run'.","For Excel mutations, use 'Sheet!Ref' notation: 'Sheet1!A1:B5'.","Use 'query' (not set/remove) for bare selector discovery — it is not guarded.","In agent/LLM pipelines, validate that mutation selectors contain a path scope before submitting."],"tags":["agent-safety","mutation-guard","selector","destructive-operation","scoped-path"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}