Skip to content

JsonPatch.TryGetValue("$.choices[0]...") throws ArgumentOutOfRangeException on empty array (regression in 2.12.0-alpha.803)Β #1281

Closed

Description

@jozkee

Summary

TryGetValue on an indexed path throws when the array is empty/out-of-range, instead of returning false. Regression between 2.12.0 and 2.12.0-alpha.803. Hits streaming chat easily, since services emit empty-choices chunks (role/filter chunk + final usage chunk when include_usage=true).

Repro

using System.ClientModel.Primitives;
using OpenAI.Chat;

var update = ModelReaderWriter.Read<StreamingChatCompletionUpdate>(
    BinaryData.FromString("""{"id":"x","object":"chat.completion.chunk","choices":[]}"""))!;

#pragma warning disable SCME0001
bool found = update.Patch.TryGetValue("$.choices[0].delta.reasoning_content"u8, out string? value);
  • 2.11.0 / 2.12.0: found=False βœ…
  • 2.12.0-alpha.803: throws ❌
System.ArgumentOutOfRangeException: Index was out of range...
   at System.Collections.Generic.List`1.get_Item(Int32 index)
   at OpenAI.Chat.StreamingChatCompletionUpdate.PropagateGet(...)
   at System.ClientModel.Primitives.JsonPatch.TryGetEncodedValueInternal(...)
   at System.ClientModel.Primitives.JsonPatch.TryGetValue(...)

Root cause

PropagateGet indexes the backing list with no bounds check (Choices[index]) β€” byte-identical in both versions, so the bug was latent. #1246 (TypeSpec generator bump) changed array backing from raw-JSON to typed-list+propagation:

- writer.WriteRawValue(Patch.GetJson("$.choices"u8));   // 2.12.0
+ Patch.WriteTo(writer, "$.choices"u8);                 // alpha.803

This now routes reads through the unguarded indexer instead of resolving safely from stored JSON.

Suggested fix

Bounds-check in generated PropagateGet:

if (!currentSlice.TryGetIndex(out int index, out _) || (uint)index >= (uint)Choices.Count)
    return false;

Impact

Microsoft.Extensions.AI.OpenAI probes "$.choices[0].delta.reasoning_content" on every streaming update -> throws under alpha.803 with Azure OpenAI:
https://github.com/dotnet/extensions/blob/6e012b3e52d8827b5a31594fa8093c4c7ce7fd5d/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs#L871-L872

Env: OpenAI 2.12.0-alpha.803 (regressed) vs 2.11.0/2.12.0 (OK); System.ClientModel 1.14.0.

Activity

  1. added
    blocked: upstreamThis issue is blocked on an upstream change, such as to the REST API spec or generator.
    area: externalThis issue is regarding an external experience, such as a companion service or client.
    and removed
    needs-triageWorkflow: This is a new issue that needs to be triaged.
    on Aug 11, 2026
  2. jsquire commented on Aug 11, 2026

    @jsquire
    Collaborator

    Hi @jozkee. Thanks for reaching out and we regret that you're experiencing difficulties.

    Thanks for the detailed report and repro. We reviewed this and confirmed the root cause. The generated PropagateGet method indexes the backing list directly (e.g. Choices[index]) after only validating that the JSON Pointer segment is a syntactically well-formed array index (TryGetIndex). There is no check of that index against the collection's actual length, so any indexed path against an empty or shorter than expected array throws ArgumentOutOfRangeException instead of TryGetValue returning false.

    This same unguarded indexer shape is present in every generated dynamic model with an array of model property, not just StreamingChatCompletionUpdate.Choices. We also confirmed it in ChatCompletion, InternalChatCompletionMessageList, and ResponseResult (Tools, OutputItems, Instructions). Your root cause and regression analysis, the #1246 generator bump changing array serialization from raw JSON pass through to typed list propagation, matches what we found.

    Because this code is generated, the durable fix needs to land in the TypeSpec C# emitter itself (BuildCollectionIfStatements in MrwSerializationTypeDefinition.Dynamic.cs, in microsoft/typespec), not as a local patch here. A local fix would be overwritten on the next generator run. We opened an upstream issue with the full technical writeup, evidence, and a suggested bounds check fix.

    microsoft/typespec#11616

    We will track that issue and pull in the generator update here once it is fixed upstream.


    πŸ€– jsquire-copilot

  3. added
    area: codegenThis item is related to code generation.
    and removed
    area: externalThis issue is regarding an external experience, such as a companion service or client.
    on Aug 11, 2026
  4. jsquire commented on Aug 13, 2026

    @jsquire
    Collaborator

    The initial fix was applied to the generator yesterday and will flow in early next week with our scheduled generator update. Once validated, we'll close this out as fully complete.

  5. MaiLinhP commented on Sep 9, 2026

    @MaiLinhP
    Collaborator

    The generator fix has been applied through the generator upgrade and validated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: codegenThis item is related to code generation.blocked: upstreamThis issue is blocked on an upstream change, such as to the REST API spec or generator.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions