Skip to content

Batch: Fixes null ErrorMessage when promoting status from MultiStatus response#5650

Merged
NaluTripician merged 3 commits intomasterfrom
users/nalutripician/fix-batch-error-message-promotion
Mar 5, 2026
Merged

Batch: Fixes null ErrorMessage when promoting status from MultiStatus response#5650
NaluTripician merged 3 commits intomasterfrom
users/nalutripician/fix-batch-error-message-promotion

Conversation

@NaluTripician
Copy link
Copy Markdown
Contributor

@NaluTripician NaluTripician commented Mar 3, 2026

Summary

Fixes #5649

When a transactional batch response returns 207 MultiStatus and the SDK promotes the status code from a failing operation (e.g., 409 Conflict), the TransactionalBatchResponse.ErrorMessage was always null. This is because only StatusCode and SubStatusCode were promoted from the failing operation — the ErrorMessage remained sourced from the outer 207 response, which has no error message.

Root Cause

In TransactionalBatchResponse.PopulateFromContentAsync(), the status promotion logic (lines 365–379) correctly promotes responseStatusCode and responseSubStatusCode from the first failing operation result, but the ErrorMessage passed to the TransactionalBatchResponse constructor was still responseMessage.ErrorMessage — the outer 207 MultiStatus message's error, which is always null.

// Before (bug):
TransactionalBatchResponse response = new TransactionalBatchResponse(
    responseStatusCode,              // 409 - promoted from operation ✅
    responseSubStatusCode,           // promoted from operation ✅
    responseMessage.ErrorMessage,    // null from outer 207 response ❌
    ...);

Fix

When promoting the status code from a failing operation, also read the error message from the failing operation's ResourceStream (which contains the server's error body for that specific operation) and use it as the promoted ErrorMessage.

// After (fix):
if (result.ResourceStream != null)
{
    using (StreamReader reader = new StreamReader(
        result.ResourceStream, encoding: Encoding.UTF8,
        detectEncodingFromByteOrderMarks: true,
        bufferSize: 1024, leaveOpen: true))
    {
        responseErrorMessage = reader.ReadToEnd();
        result.ResourceStream.Position = 0; // Reset so stream is still usable
    }
}

The stream position is reset after reading so the ResourceStream remains available to consumers who access individual operation results.

Testing

Added BatchResponseDeserializationPromotesErrorMessageAsync test that:

  1. Creates a 409 Conflict operation result with a ResourceStream containing an error body
  2. Creates a second operation result with 424 FailedDependency (as the server would)
  3. Wraps them in a 207 MultiStatus response
  4. Calls FromResponseMessageAsync with shouldPromoteOperationStatus=true
  5. Asserts StatusCode is promoted to 409
  6. Asserts ErrorMessage is promoted from the operation's ResourceStream (not null)

All 103 existing batch-related unit tests continue to pass.

…MultiStatus response

When a batch response returns 207 MultiStatus and the SDK promotes the
status code from a failing operation (e.g. 409 Conflict), also promote
the error message from the failing operation's ResourceStream.

Previously only StatusCode and SubStatusCode were promoted, leaving
ErrorMessage as null since the outer 207 response has no error message.

Fixes #5649

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@NaluTripician NaluTripician changed the title [Internal] Batch: Fixes null ErrorMessage when promoting status from MultiStatus response Batch: Fixes null ErrorMessage when promoting status from MultiStatus response Mar 3, 2026
@NaluTripician NaluTripician self-assigned this Mar 3, 2026
@NaluTripician NaluTripician added the auto-merge Enables automation to merge PRs label Mar 5, 2026
@NaluTripician NaluTripician merged commit 525d0ea into master Mar 5, 2026
33 checks passed
@NaluTripician NaluTripician deleted the users/nalutripician/fix-batch-error-message-promotion branch March 5, 2026 18:43
Copilot AI mentioned this pull request Mar 18, 2026
4 tasks
microsoft-github-policy-service Bot pushed a commit that referenced this pull request Mar 20, 2026
## Version Changes

| Property | Old | New |
|---|---|---|
| `ClientOfficialVersion` | 3.57.0 | **3.58.0** |
| `ClientPreviewVersion` | 3.58.0 | **3.59.0** |
| `ClientPreviewSuffixVersion` | preview.0 | **preview.0** |

## Changelog

### 3.59.0-preview.0 (Preview)

#### Added
- [#5502](#5502)
VectorIndex Policy: Adds Support for QuantizerType in IndexingPolicy
- [#5634](#5634)
Semantic Reranking: Adds response body in semantic reranking error
responses
- [#5685](#5685)
Read Consistency Strategy: Adds Read Consistency Strategy option for
read requests

### 3.58.0 (GA)

#### Added
- [#5447](#5447) Per
Partition Automatic Failover: Adds Hub Region Processing Only While
Routing Requests Failed with 404/1002 for single master accounts
- [#5551](#5551)
HPK: Adds internal CosmosClientOptions flag UseLengthAwareRangeComparer
for length aware range comparer rollout
- [#5582](#5582)
Query: Adds ability to choose global vs local/focused statistics for
FullTextScore
- [5610](#5610)
Refactors N-Region Synchronous Commit feature to use
IServiceConfigurationReaderVNext interface.
- [#5693](#5693)
ThinClient Integration: Adds Enable Multiple Http2 connection on
SocketsHttpHandler
- [#5614](#5614)
ThinClient Integration: Adds support for QueryPlan in thinclient mode

#### Fixed
- [#5597](#5597)
CosmosClient: Fixes ObjectDisposedException message when client is
disposed during request
- [#5613](#5613)
CrossRegionHedgingAvailabilityStrategy: Fixes ArgumentNullException race
condition in hedging cancellation
- [#5650](#5650)
Batch: Fixes null ErrorMessage when promoting status from MultiStatus
response
- [#5651](#5651)
Serializer: Fixes unsafe stream cast in FromStream<T>
- [#5697](#5697)
ResourceThrottleRetryPolicy: Fixes cumulativeRetryDelay tracking when
x-ms-retry-after-ms header is absent

### API Contract Diff (GA)

```diff
diff --git "a/Microsoft.Azure.Cosmos\\contracts\\API_3.57.0.txt" "b/Microsoft.Azure.Cosmos\\contracts\\API_3.58.0.txt"
index a1fa19e..1b74a69 100644
--- "a/Microsoft.Azure.Cosmos\\contracts\\API_3.57.0.txt"
+++ "b/Microsoft.Azure.Cosmos\\contracts\\API_3.58.0.txt"
@@ -639,6 +639,11 @@ namespace Microsoft.Azure.Cosmos
         public string DefaultLanguage { get; set; }
         public Collection<FullTextPath> FullTextPaths { get; set; }
     }
+    public enum FullTextScoreScope
+    {
+        Global = 0,
+        Local = 1,
+    }
     public sealed class GeospatialConfig
     {
         public GeospatialConfig();
@@ -869,6 +874,7 @@ namespace Microsoft.Azure.Cosmos
         public Nullable<bool> EnableLowPrecisionOrderBy { get; set; }
         public bool EnableOptimisticDirectExecution { get; set; }
         public Nullable<bool> EnableScanInQuery { get; set; }
+        public FullTextScoreScope FullTextScoreScope { get; set; }
         public Nullable<int> MaxBufferedItemCount { get; set; }
         public Nullable<int> MaxConcurrency { get; set; }
         public Nullable<int> MaxItemCount { get; set; }
```

### API Contract Diff (Preview)

```diff
diff --git "a/Microsoft.Azure.Cosmos\\contracts\\API_3.58.0-preview.0.txt" "b/Microsoft.Azure.Cosmos\\contracts\\API_3.59.0-preview.0.txt"
index af57dd8..1ae52c0 100644
--- "a/Microsoft.Azure.Cosmos\\contracts\\API_3.58.0-preview.0.txt"
+++ "b/Microsoft.Azure.Cosmos\\contracts\\API_3.59.0-preview.0.txt"
@@ -128,6 +128,7 @@ namespace Microsoft.Azure.Cosmos
         public new string IfMatchEtag { get; set; }
         public new string IfNoneMatchEtag { get; set; }
         public Nullable<int> PageSizeHint { get; set; }
+        public Nullable<ReadConsistencyStrategy> ReadConsistencyStrategy { get; set; }
     }
     public abstract class ChangeFeedStartFrom
     {
@@ -414,6 +415,7 @@ namespace Microsoft.Azure.Cosmos
         public Nullable<TimeSpan> OpenTcpConnectionTimeout { get; set; }
         public Nullable<PortReuseMode> PortReuseMode { get; set; }
         public Nullable<PriorityLevel> PriorityLevel { get; set; }
+        public Nullable<ReadConsistencyStrategy> ReadConsistencyStrategy { get; set; }
         public TimeSpan RequestTimeout { get; set; }
         public CosmosSerializer Serializer { get; set; }
         public CosmosSerializationOptions SerializerOptions { get; set; }
@@ -746,6 +748,11 @@ namespace Microsoft.Azure.Cosmos
         public string DefaultLanguage { get; set; }
         public Collection<FullTextPath> FullTextPaths { get; set; }
     }
+    public enum FullTextScoreScope
+    {
+        Global = 0,
+        Local = 1,
+    }
     public sealed class GeospatialConfig
     {
         public GeospatialConfig();
@@ -825,6 +832,7 @@ namespace Microsoft.Azure.Cosmos
         public Nullable<IndexingDirective> IndexingDirective { get; set; }
         public IEnumerable<string> PostTriggers { get; set; }
         public IEnumerable<string> PreTriggers { get; set; }
+        public Nullable<ReadConsistencyStrategy> ReadConsistencyStrategy { get; set; }
         public string SessionToken { get; set; }
     }
     public class ItemResponse<T> : Response<T>
@@ -972,6 +980,11 @@ namespace Microsoft.Azure.Cosmos
         High = 1,
         Low = 2,
     }
+    public enum QuantizerType
+    {
+        Product = 0,
+        Spherical = 1,
+    }
     public class QueryDefinition
     {
         public QueryDefinition(string query);
@@ -988,6 +1001,7 @@ namespace Microsoft.Azure.Cosmos
         public Nullable<bool> EnableLowPrecisionOrderBy { get; set; }
         public bool EnableOptimisticDirectExecution { get; set; }
         public Nullable<bool> EnableScanInQuery { get; set; }
+        public FullTextScoreScope FullTextScoreScope { get; set; }
         public Nullable<int> MaxBufferedItemCount { get; set; }
         public Nullable<int> MaxConcurrency { get; set; }
         public Nullable<int> MaxItemCount { get; set; }
@@ -995,6 +1009,7 @@ namespace Microsoft.Azure.Cosmos
         public Nullable<bool> PopulateIndexMetrics { get; set; }
         public Nullable<bool> PopulateQueryAdvice { get; set; }
         public QueryTextMode QueryTextMode { get; set; }
+        public Nullable<ReadConsistencyStrategy> ReadConsistencyStrategy { get; set; }
         public Nullable<int> ResponseContinuationTokenLimitInKb { get; set; }
         public string SessionToken { get; set; }
     }
@@ -1004,10 +1019,18 @@ namespace Microsoft.Azure.Cosmos
         None = 0,
         ParameterizedOnly = 1,
     }
+    public enum ReadConsistencyStrategy
+    {
+        Eventual = 1,
+        GlobalStrong = 4,
+        LatestCommitted = 3,
+        Session = 2,
+    }
     public class ReadManyRequestOptions : RequestOptions
     {
         public ReadManyRequestOptions();
         public Nullable<ConsistencyLevel> ConsistencyLevel { get; set; }
+        public Nullable<ReadConsistencyStrategy> ReadConsistencyStrategy { get; set; }
         public string SessionToken { get; set; }
     }
     public static class Regions
@@ -1383,6 +1406,7 @@ namespace Microsoft.Azure.Cosmos
         public int IndexingSearchListSize { get; set; }
         public string Path { get; set; }
         public int QuantizationByteSize { get; set; }
+        public Nullable<QuantizerType> QuantizerType { get; set; }
         public VectorIndexType Type { get; set; }
         public string[] VectorIndexShardKey { get; set; }
     }
@@ -1482,6 +1506,7 @@ namespace Microsoft.Azure.Cosmos.Fluent
         public CosmosClientBuilder WithHttpClientFactory(Func<HttpClient> httpClientFactory);
         public CosmosClientBuilder WithLimitToEndpoint(bool limitToEndpoint);
         public CosmosClientBuilder WithPriorityLevel(PriorityLevel priorityLevel);
+        public CosmosClientBuilder WithReadConsistencyStrategy(ReadConsistencyStrategy readConsistencyStrategy);
         public CosmosClientBuilder WithRequestTimeout(TimeSpan requestTimeout);
         public CosmosClientBuilder WithSerializerOptions(CosmosSerializationOptions cosmosSerializerOptions);
         public CosmosClientBuilder WithSystemTextJsonSerializerOptions(JsonSerializerOptions serializerOptions);
@@ -1540,6 +1565,7 @@ namespace Microsoft.Azure.Cosmos.Fluent
         public VectorIndexDefinition<T> Path(string path, VectorIndexType indexType);
         public VectorIndexDefinition<T> WithIndexingSearchListSize(int indexingSearchListSize);
         public VectorIndexDefinition<T> WithQuantizationByteSize(int quantizationByteSize);
+        public VectorIndexDefinition<T> WithQuantizerType(QuantizerType quantizerType);
         public VectorIndexDefinition<T> WithVectorIndexShardKey(string[] vectorIndexShardKey);
     }
 }
```

## Checklist
- [ ] Changelog review by team
- [ ] Email `azurecosmossdkdotnet@microsoft.com` for preview API review
- [ ] API contract diff approval (Kiran & Kirill)
- [ ] Kiran sign-off (required)
- [ ] Determine if "Recommended Version" needs further updating

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merge Enables automation to merge PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null TransactionalBatchResponse.ErrorMessage

3 participants