{"record":{"id":"961e1e83645335ba","repo":"iOfficeAI/OfficeCLI","slug":"invalid-selector-961e1e","errorCode":"invalid_selector","errorMessage":"'{selector}' is not a valid selector: a predicate must be inside brackets, e.g. row[col.2024>150] to filter table rows by a column, or cell[value>150] to filter cells.","messagePattern":"'(.+?)' is not a valid selector: a predicate must be inside brackets, e\\.g\\. row\\[col\\.2024>150\\] to filter table rows by a column, or cell\\[value>150\\] to filter cells\\.","errorType":"exception","errorClass":"Core.CliException","httpStatus":null,"severity":"error","filePath":"src/officecli/Handlers/Excel/ExcelHandler.Query.cs","lineNumber":1136,"sourceCode":"        // other Query branches. Single-cell refs return a one-element list.\n        var nativeCellRef = Regex.Match(selector, @\"^([^/!]+)!([A-Z]+\\d+(:[A-Z]+\\d+)?)$\", RegexOptions.IgnoreCase);\n        if (nativeCellRef.Success)\n        {\n            // 'My Data (2024)'!A1 — Excel requires quoting names with spaces;\n            // strip the quotes so the DOM path resolves the real sheet.\n            var node = Get($\"/{UnquoteSheetName(nativeCellRef.Groups[1].Value)}/{nativeCellRef.Groups[2].Value}\");\n            if (node.Type == \"range\" && node.Children.Count > 0)\n                return node.Children;\n            return [node];\n        }\n\n        // A comparison operator OUTSIDE any bracket means the predicate was\n        // written without its brackets (`col.2024>150`, `foo>1`, `Dept=IT`).\n        // Such a selector matches no element type and would otherwise return an\n        // empty list with exit 0 — a silent \"no rows\" that a data user reads as a\n        // real result. Fail loud with the bracketed form instead.\n        if (HasTopLevelComparison(selector))\n            throw new Core.CliException(\n                $\"'{selector}' is not a valid selector: a predicate must be inside brackets, \" +\n                $\"e.g. row[col.2024>150] to filter table rows by a column, or cell[value>150] to filter cells.\")\n                { Code = \"invalid_selector\" };\n\n        // CONSISTENCY(excel-sheet-separator-warn): Detect the PPT-style `>`\n        // separator form (e.g. `Sheet1>ole`) that users familiar with the\n        // PowerPoint query grammar may try against Excel. Excel uses `!`\n        // (Sheet1!cell[...]) — the legacy spreadsheet separator — so a `>`\n        // in the sheet-prefix slot will silently fall through to generic\n        // XML and return an empty result. We emit a single stderr warning\n        // pointing to the correct `!` form, then let the normal flow run.\n        // Only fire when the prefix looks like a sheet name (no `/`) and\n        // the suffix is a known Excel element type we would have handled.\n        {\n            var pptStyle = Regex.Match(selector, @\"^([^/!>]+)>(\\w+)\");\n            if (pptStyle.Success)\n            {\n                var suffixType = pptStyle.Groups[2].Value.ToLowerInvariant();","sourceCodeStart":1118,"sourceCodeEnd":1154,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Handlers/Excel/ExcelHandler.Query.cs#L1118-L1154","documentation":"Thrown (code invalid_selector) when a query selector contains a comparison operator (>, <, =, etc.) OUTSIDE any bracket, e.g. 'col.2024>150' or 'Dept=IT' written bare. HasTopLevelComparison detects this; without the guard the selector would match no element type and silently return an empty list with exit 0 — a 'no rows' result a user mistakes for real data. The handler fails loud and shows the correct bracketed form.","triggerScenarios":"Query/remove with a predicate whose brackets were omitted: 'row col.2024>150' or a selector string like 'col.2024>150' instead of 'row[col.2024>150]'. Any top-level comparison operator triggers it.","commonSituations":"Forgetting the square brackets around a filter expression; building selectors by string concatenation that drops the brackets; migrating from a grammar that used parens or no delimiters.","solutions":["Wrap the predicate in square brackets: row[col.2024>150] to filter table rows, or cell[value>150] to filter cells.","When constructing selectors programmatically, always template the bracket form: f\"row[col.{col}{op}{val}]\".","If you meant a path, not a filter, remove the comparison operator entirely."],"exampleFix":"// before\nget row col.2024>150\n// after\nget row[col.2024>150]","handlingStrategy":"validation","validationCode":"import re\ndef brackets_present_around_predicate(selector):\n    # a comparison operator must sit inside [...]\n    outside = re.sub(r'\\[[^\\]]*\\]', '', selector)  # strip bracketed groups\n    return not re.search(r'[<>!=]=|[<>]', outside)\n\nselector = 'col.2024>150'\nassert brackets_present_around_predicate(selector), \\\n    'predicate must be inside brackets, e.g. row[col.2024>150]'","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Always wrap predicates in square brackets: row[col.x>150].","When templating selectors, include the brackets in the template, not the value.","Strip bracketed groups and re-scan for stray operators as a pre-check."],"tags":["excel","selector","predicate","invalid-selector","query"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}