{"record":{"id":"2ff53126f4ea001f","repo":"iOfficeAI/OfficeCLI","slug":"invalid-range-spec-offsets-must-be-non-negati","errorCode":null,"errorMessage":"Invalid range '{spec}': offsets must be non-negative.","messagePattern":"Invalid range '(.+?)': offsets must be non-negative\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/ParseHelpers.cs","lineNumber":441,"sourceCode":"    }\n\n    /// <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(','))","sourceCodeStart":423,"sourceCodeEnd":459,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/ParseHelpers.cs#L423-L459","documentation":"Thrown by ParseCharRange when the spec parses as two integers but either offset is negative. Character offsets are 0-based, so negative values are invalid. This guard runs after the format check and before the end>=start check.","triggerScenarios":"Passing \"-1:5\", \"0:-3\", or an offset computed from (match - 1) when match was 0. A find-result offset that underflowed because the match was at the start of the string.","commonSituations":"Off-by-one math that yields -1 at the document start; converting 1-based offsets to 0-based by subtracting too much; an empty selection expressed as -1:-1.","solutions":["Ensure both offsets are >= 0.","Guard computed offsets: if (start < 0 || end < 0) skip/report.","Treat a no-selection case (start==end) explicitly instead of using negatives as a sentinel."],"exampleFix":"// before\nvar start = matchIndex - 1; // matchIndex 0 -> -1\nvar spec = $\"{start}:{end}\";\nParseCharRange(spec); // throws 299\n\n// after\nvar start = Math.Max(0, matchIndex);\nvar spec = $\"{start}:{end}\";\nParseCharRange(spec);","handlingStrategy":"validation","validationCode":"if (start < 0 || end < 0)\n{ start = Math.Max(0, start); end = Math.Max(0, end); }","typeGuard":"static bool AreNonNegativeOffsets(string spec)\n{ var p = spec.Split(':');\n  return int.TryParse(p[0].Trim(), CultureInfo.InvariantCulture, out var s)\n      && int.TryParse(p[1].Trim(), CultureInfo.InvariantCulture, out var e)\n      && s >= 0 && e >= 0; }","tryCatchPattern":"try { range = ParseCharRange(spec); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"non-negative\"))\n{ /* clamp offsets to 0 and retry */ }","preventionTips":["Clamp computed offsets to >= 0 before formatting.","Handle the at-start-of-document case without negative sentinels.","Guard find-offset math against underflow."],"tags":["range","validation","argument","range"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}