{"record":{"id":"dd860fa4fc8cef19","repo":"iOfficeAI/OfficeCLI","slug":"progid-progid-cannot-start-with-a-digit","errorCode":null,"errorMessage":"progId '{progId}' cannot start with a digit.","messagePattern":"progId '(.+?)' cannot start with a digit\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/OleHelper.cs","lineNumber":495,"sourceCode":"    public const long DefaultOleWidthEmu = 1828800;  // 2 inches\n    public const long DefaultOleHeightEmu = 685800;   //  0.75 inches\n\n    /// <summary>\n    /// Validate a COM ProgID string against the well-known Windows COM\n    /// constraints: the identifier must be 1..39 characters long and must\n    /// not start with a digit. OLE spec (MSDN \"ProgID\") is explicit on both\n    /// rules. Handlers previously accepted arbitrary strings silently; this\n    /// method gives users an early, actionable error instead of writing an\n    /// invalid OLE element that Office refuses to open.\n    /// </summary>\n    public static void ValidateProgId(string progId)\n    {\n        if (progId == null) return;\n        if (progId.Length > 39)\n            throw new ArgumentException(\n                $\"progId '{progId}' exceeds 39 characters (limit: 39, actual: {progId.Length}).\");\n        if (progId.Length > 0 && char.IsDigit(progId[0]))\n            throw new ArgumentException(\n                $\"progId '{progId}' cannot start with a digit.\");\n        // COM ProgID character set: letters, digits, '.', '_', '-'. Anything\n        // else (notably XML-unsafe characters like '<', '>', '&', '\"') would\n        // either corrupt the OOXML progId attribute or be rejected by Office\n        // on reopen. Reject early with an actionable error instead of letting\n        // bad bytes land in the package.\n        foreach (var ch in progId)\n        {\n            if (!(char.IsLetterOrDigit(ch) || ch == '.' || ch == '_' || ch == '-'))\n                throw new ArgumentException(\n                    $\"progId '{progId}' contains invalid characters. Only letters, digits, '.', '_', '-' are allowed.\");\n        }\n    }\n\n    /// <summary>\n    /// Normalize and validate the caller-supplied <c>display</c> property\n    /// for an OLE object. Canonical values are <c>\"icon\"</c> (show the file\n    /// as a clickable icon preview) and <c>\"content\"</c> (show the embedded","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/OleHelper.cs#L477-L513","documentation":"Thrown by OleHelper.ValidateProgId when a non-empty progId string starts with a digit (0-9). The Windows COM specification explicitly forbids ProgIDs that begin with a digit. Writing such a progId into the OOXML attribute would produce an OLE element that Office cannot activate or may silently misbehave on. The check is char.IsDigit(progId[0]) after the length check passes, so an empty string does NOT trigger this (it's allowed through as a no-op at this stage).","triggerScenarios":"Calling Add ole or Set ole with a progId like '123Doc', '2pdf', or '4MyApp'. The first character is checked with char.IsDigit, so any Unicode digit (not just ASCII 0-9) triggers it. ValidateProgId is invoked from ResolveProgId in all three handlers.","commonSituations":"A user who starts a custom progId with a number. A progId auto-generated from a filename that starts with a digit (e.g. '2024report.pdf' → progId '2024report'). A copy-paste error where a version number was prepended to a progId.","solutions":["Start the progId with a letter instead of a digit (e.g. 'App2Doc' instead of '2AppDoc').","Use a standard Office progId (which always starts with a letter: 'Word.Document.12', 'Excel.Sheet.12', etc.).","Prefix a digit-starting identifier with a letter (e.g. 'D' + originalName)."],"exampleFix":"// before — progId starts with digit\nadd ole src=file.pdf progId=2PdfHandler path='/body'\n\n// after — starts with a letter\nadd ole src=file.pdf progId=Pdf2Handler path='/body'\n// or use a standard progId\nadd ole src=file.pdf progId=Package path='/body'","handlingStrategy":"validation","validationCode":"// Pre-validate progId does not start with a digit\nif (!string.IsNullOrEmpty(progId) && char.IsDigit(progId[0]))\n{\n    Console.Error.WriteLine($\"progId '{progId}' cannot start with a digit. Prefix with a letter.\");\n    progId = \"D\" + progId; // or use a standard progId\n}\nOleHelper.ValidateProgId(progId);","typeGuard":null,"tryCatchPattern":"try\n{\n    OleHelper.ValidateProgId(progId);\n}\ncatch (ArgumentException ex) when (ex.Message.Contains(\"cannot start with a digit\"))\n{\n    progId = \"A\" + progId;\n    OleHelper.ValidateProgId(progId);\n}","preventionTips":["Ensure custom progIds start with a letter, not a digit.","When auto-generating progIds from filenames, strip or prefix leading digits.","Use OleHelper.DetectProgId for automatic progId assignment from file extension."],"tags":["ole","progid","com-validation","naming-convention","argument-validation"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}