{"record":{"id":"bd039103ebb3c060","repo":"iOfficeAI/OfficeCLI","slug":"invalid-range-spec-end-end-must-be-sta","errorCode":null,"errorMessage":"Invalid range '{spec}': end ({end}) must be >= start ({start}).","messagePattern":"Invalid range '(.+?)': end \\((.+?)\\) must be >= start \\((.+?)\\)\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/ParseHelpers.cs","lineNumber":443,"sourceCode":"    /// <summary>\n    /// Parse a \"start:end\" character-range spec into 0-based, half-open offsets.\n    /// Colon separator mirrors the officecli range convention (Excel A1:B2).\n    /// Shared by the pptx and docx run-range formatting paths so the two never\n    /// diverge (CONSISTENCY(char-range)).\n    /// </summary>\n    public static (int Start, int End) ParseCharRange(string spec)\n    {\n        var parts = spec.Split(':');\n        if (parts.Length != 2\n            || !int.TryParse(parts[0].Trim(), CultureInfo.InvariantCulture, out var start)\n            || !int.TryParse(parts[1].Trim(), CultureInfo.InvariantCulture, out var end))\n            throw new ArgumentException(\n                $\"Invalid range '{spec}'. Expected 'start:end' with 0-based integer \" +\n                \"character offsets (e.g. '6:11').\");\n        if (start < 0 || end < 0)\n            throw new ArgumentException($\"Invalid range '{spec}': offsets must be non-negative.\");\n        if (end < start)\n            throw new ArgumentException($\"Invalid range '{spec}': end ({end}) must be >= start ({start}).\");\n        return (start, end);\n    }\n\n    /// <summary>\n    /// Parse a comma-separated list of \"start:end\" character ranges into 0-based,\n    /// half-open offset pairs (e.g. \"6:11,20:25\" → [(6,11),(20,25)]). A single\n    /// range needs no comma. This lets one range= command target several disjoint\n    /// spans — the same shape find's format path produces from multiple matches —\n    /// so range is a complete addressing alternative wherever the caller already\n    /// knows the offsets. Order is preserved; the caller applies them (format-only\n    /// run splitting does not shift character offsets, so any order is safe).\n    /// </summary>\n    public static List<(int Start, int End)> ParseCharRanges(string spec)\n    {\n        var result = new List<(int Start, int End)>();\n        foreach (var seg in spec.Split(','))\n        {\n            var trimmed = seg.Trim();","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/ParseHelpers.cs#L425-L461","documentation":"Thrown by ParseHelpers.ParseCharRange when a 'start:end' character-offset range has end < start. The function parses CLI/JSON 'range=' specs into 0-based half-open offset pairs (used by Word/PPT Set to format a targeted character span). Offsets must be non-negative integers and end must be >= start.","triggerScenarios":"Calling PowerPointHandler.Set or WordHandler.Set with a 'range' property whose start offset exceeds the end offset, e.g. range=\"11:6\" or range=\"20:5\". ParseCharRange splits on ':', parses both halves as int, then checks end < start.","commonSituations":"Swapping start/end when hand-deriving offsets from a 'find' match; assuming the range is inclusive-right and subtracting one from the end (making end=start-1); off-by-one when converting 1-based visual positions to 0-based offsets.","solutions":["Swap the offsets so start <= end (e.g. range=\"6:11\" instead of \"11:6\").","If you derived the pair from a 'find' match, remember offsets are 0-based and half-open: start is the match start index, end is the start+length (or the match end index).","For multiple spans use range=\"6:11,20:25\" — ParseCharRanges sorts them ascending, but each individual range must still satisfy end>=start."],"exampleFix":"// before\nrange=\"11:6\"\n// after\nrange=\"6:11\"","handlingStrategy":"validation","validationCode":"// Before calling ParseCharRange / ParseCharRanges, verify each 'start:end' pair:\nstatic bool IsValidRangeSpec(string spec)\n{\n    foreach (var seg in spec.Split(','))\n    {\n        var t = seg.Trim();\n        if (t.Length == 0) continue;\n        var p = t.Split(':');\n        if (p.Length != 2\n            || !int.TryParse(p[0].Trim(), System.Globalization.CultureInfo.InvariantCulture, out var s)\n            || !int.TryParse(p[1].Trim(), System.Globalization.CultureInfo.InvariantCulture, out var e)\n            || s < 0 || e < 0 || e < s)\n            return false;\n    }\n    return true;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive offsets from find-match indices (0-based, half-open) rather than hand-typing them.","Always order start <= end before joining into the spec.","When joining multiple ranges programmatically, skip empty segments."],"tags":["parsing","input-validation","range","officecli","offsets"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}