iOfficeAI/OfficeCLI · error · ArgumentException

{propName} contains an unpaired low surrogate U+{(int)c:X4}

Error message

{propName} contains an unpaired low surrogate U+{(int)c:X4} at position {i}. Use a complete UTF-16 surrogate pair.

What it means

Thrown by ValidateXmlText when a low surrogate (U+DC00–U+DFFF) appears without a preceding high surrogate. Low surrogates are only valid as the second half of a pair; a lone low surrogate is illegal in XML 1.0 character data.

Source

Thrown at src/officecli/Core/ParseHelpers.cs:866

            // strict default so '\v' can never reach raw character data.
            if (c == '\v' && allowSoftBreakChar) continue;
            if (c < 0x20)
                throw new ArgumentException(
                    $"{propName} contains XML-illegal control character U+{(int)c:X4} at position {i}. " +
                    "Allowed control chars: \\t, \\n, \\r" +
                    (allowSoftBreakChar ? ", \\v." : "."));
            // UTF-16 surrogates only valid in pairs (high then low). A lone
            // half is illegal in XML 1.0 character data.
            if (char.IsHighSurrogate(c))
            {
                if (i + 1 >= value.Length || !char.IsLowSurrogate(value[i + 1]))
                    throw new ArgumentException(
                        $"{propName} contains an unpaired high surrogate U+{(int)c:X4} at position {i}. Use a complete UTF-16 surrogate pair.");
                i++; // skip the matched low surrogate
                continue;
            }
            if (char.IsLowSurrogate(c))
                throw new ArgumentException(
                    $"{propName} contains an unpaired low surrogate U+{(int)c:X4} at position {i}. Use a complete UTF-16 surrogate pair.");
            if (c == 0xFFFE || c == 0xFFFF)
                throw new ArgumentException(
                    $"{propName} contains the XML-illegal noncharacter U+{(int)c:X4} at position {i}.");
        }
    }
}

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Reconstruct or re-encode the original text so every low surrogate is preceded by its matching high surrogate.
  2. Use text-element-aware APIs (StringInfo) for substring/remove operations on text that may contain supplementary-plane characters.
  3. Filter out lone surrogates before setting the value if you cannot recover the pair.

Example fix

// before
var s = "\uDE00";            // lone low surrogate
// after
var s = "\uD83D\uDE00";      // complete pair
Defensive patterns

Strategy: validation

Validate before calling

static bool HasNoLoneSurrogates(string s)
{
    for (int i = 0; i < s.Length; i++)
    {
        if (char.IsHighSurrogate(s, i) && (i + 1 >= s.Length || !char.IsLowSurrogate(s[i + 1]))) return false;
        if (char.IsLowSurrogate(s, i) && (i == 0 || !char.IsHighSurrogate(s[i - 1]))) return false;
    }
    return true;
}

Prevention

When it happens

Trigger: A string that starts with or contains an isolated low surrogate, e.g. from slicing a pair and keeping the trailing half, or from manually constructing chars from raw code units where the high half was dropped.

Common situations: String slicing/removal that deleted the high half of an emoji but kept the low half; decoding errors; constructing strings from unvalidated char arrays.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/7c9cd712c96a2cee. Report an issue: GitHub.