From e337f59413d0e0dc2d2c68969bb2d92e12622d5f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 23 May 2026 04:22:24 +0000
Subject: [PATCH 1/3] Document Cosmos Linux emulator failures behind the skip
condition (issue #291)
Agent-Logs-Url: https://github.com/dotnet/efcore/sessions/4f55855e-7fc7-42f6-b8d2-537427c6beb8
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../01-invalid-session-token-error-message.md | 99 ++++++++++++++
.../02-future-session-token-ignored.md | 109 +++++++++++++++
.../03-failed-write-advances-session-token.md | 125 ++++++++++++++++++
3 files changed, 333 insertions(+)
create mode 100644 docs/cosmos-emulator-issues/01-invalid-session-token-error-message.md
create mode 100644 docs/cosmos-emulator-issues/02-future-session-token-ignored.md
create mode 100644 docs/cosmos-emulator-issues/03-failed-write-advances-session-token.md
diff --git a/docs/cosmos-emulator-issues/01-invalid-session-token-error-message.md b/docs/cosmos-emulator-issues/01-invalid-session-token-error-message.md
new file mode 100644
index 00000000000..9cdd05d387a
--- /dev/null
+++ b/docs/cosmos-emulator-issues/01-invalid-session-token-error-message.md
@@ -0,0 +1,99 @@
+# Linux Cosmos DB emulator returns a different error message for an invalid session token
+
+Tracking issue: [Azure/azure-cosmos-db-emulator-docker#291](https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/291)
+
+## Summary
+
+When the SDK passes a syntactically-invalid value as the `SessionToken` request header, the **Linux** Cosmos DB emulator
+(`mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview`) returns the error string
+
+> `The session token provided 'invalidtoken' is not valid.`
+
+The **Windows** emulator and the real Cosmos DB service instead return
+
+> `The session token provided 'invalidtoken' is invalid.`
+
+Both responses are `400 BadRequest`, but the wording is different (`is not valid` vs. `is invalid`). Code that
+asserts on the exact message text (which is what we do in our EF Core tests) fails on the Linux emulator.
+
+This is the failure observed in 21 of the 26 EF Core
+`CosmosSessionTokensTest` failures (the `Query_uses_session_token`,
+`Read_item_uses_session_token`, `Shaped_query_uses_session_token`,
+`PagingQuery_uses_session_token`, and all `Add_uses_GetSessionToken` /
+`Update_uses_session_token` / `Delete_uses_session_token` theory rows).
+
+## Stand-alone repro (no EF Core)
+
+`Program.csproj`:
+
+```xml
+
+
+ Exe
+ net8.0
+
+
+
+
+
+```
+
+`Program.cs`:
+
+```csharp
+using System.Net.Http;
+using Microsoft.Azure.Cosmos;
+
+const string Endpoint = "https://localhost:8081";
+const string Key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
+
+var options = new CosmosClientOptions
+{
+ ConnectionMode = ConnectionMode.Gateway,
+ ConsistencyLevel = ConsistencyLevel.Session,
+ HttpClientFactory = () => new HttpClient(new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
+ })
+};
+
+using var client = new CosmosClient(Endpoint, Key, options);
+var db = (await client.CreateDatabaseIfNotExistsAsync("ReproDb")).Database;
+var container = (await db.CreateContainerIfNotExistsAsync(
+ new ContainerProperties("Repro", "/pk"))).Container;
+
+await container.UpsertItemAsync(new { id = "1", pk = "1" }, new PartitionKey("1"));
+
+try
+{
+ await container.ReadItemAsync(
+ "1", new PartitionKey("1"),
+ new ItemRequestOptions { SessionToken = "invalidtoken" });
+}
+catch (CosmosException ex)
+{
+ // Real Cosmos / Windows emulator:
+ // "The session token provided 'invalidtoken' is invalid."
+ // Linux emulator:
+ // "The session token provided 'invalidtoken' is not valid."
+ Console.WriteLine(ex.ResponseBody);
+}
+```
+
+Expected output (real Cosmos / Windows emulator):
+
+```
+code : BadRequest
+message : The session token provided 'invalidtoken' is invalid.
+```
+
+Actual output on the Linux emulator:
+
+```
+code : BadRequest
+message : The session token provided 'invalidtoken' is not valid.
+```
+
+## Suggested fix
+
+Align the Linux emulator's error message with the real service so that the substring `is invalid` is preserved.
diff --git a/docs/cosmos-emulator-issues/02-future-session-token-ignored.md b/docs/cosmos-emulator-issues/02-future-session-token-ignored.md
new file mode 100644
index 00000000000..3365ab7f9e4
--- /dev/null
+++ b/docs/cosmos-emulator-issues/02-future-session-token-ignored.md
@@ -0,0 +1,109 @@
+# Linux Cosmos DB emulator silently accepts an unreachable (future) session token
+
+Tracking issue: [Azure/azure-cosmos-db-emulator-docker#291](https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/291)
+
+## Summary
+
+When a client passes a session token whose LSN is far in the future (one that the server can never satisfy because no
+such write has occurred), the **real Cosmos DB service** and the **Windows** emulator block briefly waiting for the
+session to become available and then return `404 NotFound` with sub-status `1002` and the message
+
+> `The read session is not available for the input session token.`
+
+The **Linux** emulator (`mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview`) instead **silently
+returns the item with status `200 OK`**, completely ignoring the session token.
+
+This causes the following EF Core `CosmosSessionTokensTest+CosmosNonSharedSessionTokenTests` tests to fail because the
+expected `CosmosException` is never thrown:
+
+- `UseSessionTokens_uses_session_tokens`
+- `Read_item_session_not_found_throws_CosmosException`
+
+## Stand-alone repro (no EF Core)
+
+`Program.csproj`:
+
+```xml
+
+
+ Exe
+ net8.0
+
+
+
+
+
+```
+
+`Program.cs`:
+
+```csharp
+using System.Net.Http;
+using Microsoft.Azure.Cosmos;
+
+const string Endpoint = "https://localhost:8081";
+const string Key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
+
+var options = new CosmosClientOptions
+{
+ ConnectionMode = ConnectionMode.Gateway,
+ ConsistencyLevel = ConsistencyLevel.Session,
+ HttpClientFactory = () => new HttpClient(new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
+ })
+};
+
+using var client = new CosmosClient(Endpoint, Key, options);
+var db = (await client.CreateDatabaseIfNotExistsAsync("ReproDb")).Database;
+var container = (await db.CreateContainerIfNotExistsAsync(
+ new ContainerProperties("ReproFuture", "/pk"))).Container;
+
+// Write an item to obtain a valid session token in the form ":<...>#".
+var write = await container.UpsertItemAsync(
+ new { id = "1", pk = "1" }, new PartitionKey("1"));
+var valid = write.Headers.Session; // e.g. "0:0#5"
+
+// Build a token in the same range but with LSN = int.MaxValue.
+var hash = valid.IndexOf('#');
+var future = valid.Substring(0, hash + 1) + int.MaxValue;
+Console.WriteLine($"valid = {valid}");
+Console.WriteLine($"future = {future}");
+
+try
+{
+ var result = await container.ReadItemAsync(
+ "1", new PartitionKey("1"),
+ new ItemRequestOptions { SessionToken = future });
+
+ // Linux emulator: prints "200 OK" (ignored the unsatisfiable session token).
+ Console.WriteLine($"Read succeeded with StatusCode={result.StatusCode}");
+}
+catch (CosmosException ex)
+{
+ // Real Cosmos / Windows emulator: 404 with the standard "read session is not available" message.
+ Console.WriteLine($"Status={ex.StatusCode} SubStatus={ex.SubStatusCode}");
+ Console.WriteLine(ex.ResponseBody);
+}
+```
+
+Expected output (real Cosmos / Windows emulator):
+
+```
+Status=NotFound SubStatus=1002
+... The read session is not available for the input session token. ...
+```
+
+Actual output on the Linux emulator:
+
+```
+Read succeeded with StatusCode=OK
+```
+
+## Suggested fix
+
+The emulator should honor the session-token contract: when a client sends a session token whose LSN is greater than the
+current max global LSN for the target partition, the request must either (a) wait for the session to become available
+up to the configured timeout, or (b) return `404 NotFound` with sub-status `1002`
+(`The read session is not available for the input session token.`), as the real service does. Returning `200 OK` while
+ignoring the session token breaks session-consistency guarantees for clients that rely on causal reads.
diff --git a/docs/cosmos-emulator-issues/03-failed-write-advances-session-token.md b/docs/cosmos-emulator-issues/03-failed-write-advances-session-token.md
new file mode 100644
index 00000000000..b72006333f9
--- /dev/null
+++ b/docs/cosmos-emulator-issues/03-failed-write-advances-session-token.md
@@ -0,0 +1,125 @@
+# Linux Cosmos DB emulator returns an extra session-token LSN across multi-context concurrency sequences
+
+Tracking issue: [Azure/azure-cosmos-db-emulator-docker#291](https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/291)
+
+## Summary
+
+While running EF Core's `CosmosSessionTokensTest+CosmosNonSharedSessionTokenTests.Optimistic_concurrency_precondition_failure_updates_session_token`
+against the **Linux** emulator we observe that the set of session tokens EF Core sees across a multi-client
+optimistic-concurrency sequence contains **one extra LSN value** that the same sequence does not produce against the
+**Windows** emulator or the real Cosmos DB service.
+
+Specifically, EF Core's `CompositeSessionToken` (which accumulates every distinct session-token response value the SDK
+hands it during a logical sequence) collects:
+
+| Environment | Tokens observed (joined) |
+| --- | --- |
+| Real Cosmos / Windows emulator | `0:0#51,0:0#52,0:0#0,0:0#54` |
+| Linux emulator | `0:0#51,0:0#52,0:0#0,0:0#53,0:0#54` |
+
+Note the extra `0:0#53` slipping in between `0:0#0` and `0:0#54`. That extra LSN was returned in a response (most likely
+to a *failed* write — either the 412 from the stale-ETag `Replace`, or the 412/404 from the subsequent stale-ETag
+`Delete`) that should not have advanced the partition's session LSN.
+
+The failing assertions are:
+
+- `Optimistic_concurrency_precondition_failure_updates_session_token(autoTransactionBehavior: Always)`
+- `Optimistic_concurrency_precondition_failure_updates_session_token(autoTransactionBehavior: Never)`
+
+## Stand-alone repro (no EF Core)
+
+The simplest deterministic repro is a two-"client" sequence:
+client A creates a document, client B reads then replaces it, then client A
+tries to replace using its now-stale ETag (and again to delete with the stale ETag). Capture the session token
+returned in each response and look for an LSN that only appears under the Linux emulator.
+
+`Program.csproj`:
+
+```xml
+
+
+ Exe
+ net8.0
+
+
+
+
+
+```
+
+`Program.cs`:
+
+```csharp
+using System.Net.Http;
+using Microsoft.Azure.Cosmos;
+
+const string Endpoint = "https://localhost:8081";
+const string Key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
+
+var options = new CosmosClientOptions
+{
+ ConnectionMode = ConnectionMode.Gateway,
+ ConsistencyLevel = ConsistencyLevel.Session,
+ HttpClientFactory = () => new HttpClient(new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
+ })
+};
+
+using var client = new CosmosClient(Endpoint, Key, options);
+var db = (await client.CreateDatabaseIfNotExistsAsync("ReproDb")).Database;
+var c = (await db.CreateContainerIfNotExistsAsync(new ContainerProperties("ReproEtag", "/pk"))).Container;
+
+void Log(string label, string? session) => Console.WriteLine($"{label,-12} session={session}");
+
+// Client A creates
+var a1 = await c.CreateItemAsync(new { id = "1", pk = "1", v = 1 }, new PartitionKey("1"));
+var staleEtag = a1.ETag;
+Log("A create", a1.Headers.Session);
+
+// Client B reads and updates
+var b1 = await c.ReadItemAsync("1", new PartitionKey("1"));
+Log("B read", b1.Headers.Session);
+
+var b2 = await c.ReplaceItemAsync(new { id = "1", pk = "1", v = 2 }, "1", new PartitionKey("1"),
+ new ItemRequestOptions { IfMatchEtag = b1.ETag });
+Log("B replace", b2.Headers.Session);
+var latestSuccessfulSession = b2.Headers.Session;
+
+// Client A tries to replace using its stale ETag -> 412 (FAILED write)
+try
+{
+ await c.ReplaceItemAsync(new { id = "1", pk = "1", v = 3 }, "1", new PartitionKey("1"),
+ new ItemRequestOptions { IfMatchEtag = staleEtag });
+}
+catch (CosmosException ex)
+{
+ Log("A replace*", ex.Headers?.Session);
+ // EXPECTED (real Cosmos / Windows emulator): same as 'B replace' (failed write does not advance LSN).
+ // OBSERVED (Linux emulator): may differ by 1 LSN.
+}
+
+// Client A tries to delete using its stale ETag -> 412 (FAILED write)
+try
+{
+ await c.DeleteItemAsync("1", new PartitionKey("1"),
+ new ItemRequestOptions { IfMatchEtag = staleEtag });
+}
+catch (CosmosException ex)
+{
+ Log("A delete*", ex.Headers?.Session);
+}
+```
+
+Expected on the real service: every session token observed after `B replace` equals `latestSuccessfulSession` until the
+next successful write. Observed on the Linux emulator: at least one of the failed-write responses returns a session
+token with a higher LSN than `latestSuccessfulSession`, which is then accumulated by EF Core's `CompositeSessionToken`
+and causes the equality assertion to fail.
+
+## Suggested fix
+
+A failed write (412 PreconditionFailed, 404 NotFound on Delete/Replace, etc.) must not bump the partition's session
+LSN, and the response header `x-ms-session-token` must reflect the last committed LSN — matching the behaviour of the
+real Cosmos DB service and the Windows emulator. Otherwise, callers relying on session-token equality across
+contexts to verify causality and detect concurrency outcomes (as the EF Core Cosmos provider does) see spurious
+extra session tokens.
From 2546a196a1d6d8436f4e0a92b27b9a0a9133f76e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 23 May 2026 04:31:08 +0000
Subject: [PATCH 2/3] Cosmos: relax invalid-session-token assertion to match
Linux and Windows emulator wording
Agent-Logs-Url: https://github.com/dotnet/efcore/sessions/45f81279-a6cc-4d8e-8d4c-4c8b5ef2dc56
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../CosmosSessionTokensTest.cs | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/test/EFCore.Cosmos.FunctionalTests/CosmosSessionTokensTest.cs b/test/EFCore.Cosmos.FunctionalTests/CosmosSessionTokensTest.cs
index 2a99113b791..cbd20b6bea1 100644
--- a/test/EFCore.Cosmos.FunctionalTests/CosmosSessionTokensTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/CosmosSessionTokensTest.cs
@@ -7,8 +7,6 @@
namespace Microsoft.EntityFrameworkCore;
-// https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/291 (Session tokens not properly tracked)
-[CosmosCondition(CosmosCondition.IsNotLinuxEmulator)]
public class CosmosSessionTokensTest(CosmosSessionTokensTest.CosmosFixture fixture) : IClassFixture
{
private const string DatabaseName = nameof(CosmosSessionTokensTest);
@@ -81,7 +79,7 @@ await Assert.ThrowsAsync(() => context.OtherContainerCustomers.
foreach (var ex in exes)
{
- Assert.Contains("The session token provided 'invalidtoken' is invalid", ex.ResponseBody);
+ Assert.Contains("The session token provided 'invalidtoken' is", ex.ResponseBody);
}
}
@@ -100,7 +98,7 @@ await Assert.ThrowsAsync(() => context.OtherContainerCustomers.
foreach (var ex in exes)
{
- Assert.Contains("The session token provided 'invalidtoken' is invalid", ex.ResponseBody);
+ Assert.Contains("The session token provided 'invalidtoken' is", ex.ResponseBody);
}
}
@@ -119,7 +117,7 @@ public virtual async Task Shaped_query_uses_session_token()
foreach (var ex in exes)
{
- Assert.Contains("The session token provided 'invalidtoken' is invalid", ex.ResponseBody);
+ Assert.Contains("The session token provided 'invalidtoken' is", ex.ResponseBody);
}
}
@@ -138,7 +136,7 @@ await Assert.ThrowsAsync(() => context.OtherContainerCustomers.
foreach (var ex in exes)
{
- Assert.Contains("The session token provided 'invalidtoken' is invalid", ex.ResponseBody);
+ Assert.Contains("The session token provided 'invalidtoken' is", ex.ResponseBody);
}
}
@@ -479,7 +477,7 @@ public virtual async Task Add_uses_GetSessionToken(AutoTransactionBehavior autoT
var ex = await Assert.ThrowsAsync(() => context.SaveChangesAsync());
- Assert.Contains("The session token provided 'invalidtoken' is invalid.", ((CosmosException)ex.InnerException!).ResponseBody);
+ Assert.Contains("The session token provided 'invalidtoken' is", ((CosmosException)ex.InnerException!).ResponseBody);
}
[ConditionalTheory]
@@ -510,7 +508,7 @@ public virtual async Task Update_uses_session_token(AutoTransactionBehavior auto
var ex = await Assert.ThrowsAsync(() => context.SaveChangesAsync());
- Assert.Contains("The session token provided 'invalidtoken' is invalid.", ((CosmosException)ex.InnerException!).ResponseBody);
+ Assert.Contains("The session token provided 'invalidtoken' is", ((CosmosException)ex.InnerException!).ResponseBody);
}
[ConditionalTheory]
@@ -541,7 +539,7 @@ public virtual async Task Delete_uses_session_token(AutoTransactionBehavior auto
var ex = await Assert.ThrowsAsync(() => context.SaveChangesAsync());
- Assert.Contains("The session token provided 'invalidtoken' is invalid.", ((CosmosException)ex.InnerException!).ResponseBody);
+ Assert.Contains("The session token provided 'invalidtoken' is", ((CosmosException)ex.InnerException!).ResponseBody);
}
[ConditionalFact]
From 923e6c610c19c792ccbb57e38c78aa606173cff9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 23 May 2026 04:46:21 +0000
Subject: [PATCH 3/3] Cosmos: replace class-level Linux-emulator skip with
per-test skips citing emulator issues #322 and #319; remove diagnostic md
files
Agent-Logs-Url: https://github.com/dotnet/efcore/sessions/773a262b-df07-464f-9364-92c079d190c7
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../01-invalid-session-token-error-message.md | 99 --------------
.../02-future-session-token-ignored.md | 109 ---------------
.../03-failed-write-advances-session-token.md | 125 ------------------
.../CosmosSessionTokensTest.cs | 8 +-
4 files changed, 6 insertions(+), 335 deletions(-)
delete mode 100644 docs/cosmos-emulator-issues/01-invalid-session-token-error-message.md
delete mode 100644 docs/cosmos-emulator-issues/02-future-session-token-ignored.md
delete mode 100644 docs/cosmos-emulator-issues/03-failed-write-advances-session-token.md
diff --git a/docs/cosmos-emulator-issues/01-invalid-session-token-error-message.md b/docs/cosmos-emulator-issues/01-invalid-session-token-error-message.md
deleted file mode 100644
index 9cdd05d387a..00000000000
--- a/docs/cosmos-emulator-issues/01-invalid-session-token-error-message.md
+++ /dev/null
@@ -1,99 +0,0 @@
-# Linux Cosmos DB emulator returns a different error message for an invalid session token
-
-Tracking issue: [Azure/azure-cosmos-db-emulator-docker#291](https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/291)
-
-## Summary
-
-When the SDK passes a syntactically-invalid value as the `SessionToken` request header, the **Linux** Cosmos DB emulator
-(`mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview`) returns the error string
-
-> `The session token provided 'invalidtoken' is not valid.`
-
-The **Windows** emulator and the real Cosmos DB service instead return
-
-> `The session token provided 'invalidtoken' is invalid.`
-
-Both responses are `400 BadRequest`, but the wording is different (`is not valid` vs. `is invalid`). Code that
-asserts on the exact message text (which is what we do in our EF Core tests) fails on the Linux emulator.
-
-This is the failure observed in 21 of the 26 EF Core
-`CosmosSessionTokensTest` failures (the `Query_uses_session_token`,
-`Read_item_uses_session_token`, `Shaped_query_uses_session_token`,
-`PagingQuery_uses_session_token`, and all `Add_uses_GetSessionToken` /
-`Update_uses_session_token` / `Delete_uses_session_token` theory rows).
-
-## Stand-alone repro (no EF Core)
-
-`Program.csproj`:
-
-```xml
-
-
- Exe
- net8.0
-
-
-
-
-
-```
-
-`Program.cs`:
-
-```csharp
-using System.Net.Http;
-using Microsoft.Azure.Cosmos;
-
-const string Endpoint = "https://localhost:8081";
-const string Key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
-
-var options = new CosmosClientOptions
-{
- ConnectionMode = ConnectionMode.Gateway,
- ConsistencyLevel = ConsistencyLevel.Session,
- HttpClientFactory = () => new HttpClient(new HttpClientHandler
- {
- ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
- })
-};
-
-using var client = new CosmosClient(Endpoint, Key, options);
-var db = (await client.CreateDatabaseIfNotExistsAsync("ReproDb")).Database;
-var container = (await db.CreateContainerIfNotExistsAsync(
- new ContainerProperties("Repro", "/pk"))).Container;
-
-await container.UpsertItemAsync(new { id = "1", pk = "1" }, new PartitionKey("1"));
-
-try
-{
- await container.ReadItemAsync(
- "1", new PartitionKey("1"),
- new ItemRequestOptions { SessionToken = "invalidtoken" });
-}
-catch (CosmosException ex)
-{
- // Real Cosmos / Windows emulator:
- // "The session token provided 'invalidtoken' is invalid."
- // Linux emulator:
- // "The session token provided 'invalidtoken' is not valid."
- Console.WriteLine(ex.ResponseBody);
-}
-```
-
-Expected output (real Cosmos / Windows emulator):
-
-```
-code : BadRequest
-message : The session token provided 'invalidtoken' is invalid.
-```
-
-Actual output on the Linux emulator:
-
-```
-code : BadRequest
-message : The session token provided 'invalidtoken' is not valid.
-```
-
-## Suggested fix
-
-Align the Linux emulator's error message with the real service so that the substring `is invalid` is preserved.
diff --git a/docs/cosmos-emulator-issues/02-future-session-token-ignored.md b/docs/cosmos-emulator-issues/02-future-session-token-ignored.md
deleted file mode 100644
index 3365ab7f9e4..00000000000
--- a/docs/cosmos-emulator-issues/02-future-session-token-ignored.md
+++ /dev/null
@@ -1,109 +0,0 @@
-# Linux Cosmos DB emulator silently accepts an unreachable (future) session token
-
-Tracking issue: [Azure/azure-cosmos-db-emulator-docker#291](https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/291)
-
-## Summary
-
-When a client passes a session token whose LSN is far in the future (one that the server can never satisfy because no
-such write has occurred), the **real Cosmos DB service** and the **Windows** emulator block briefly waiting for the
-session to become available and then return `404 NotFound` with sub-status `1002` and the message
-
-> `The read session is not available for the input session token.`
-
-The **Linux** emulator (`mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview`) instead **silently
-returns the item with status `200 OK`**, completely ignoring the session token.
-
-This causes the following EF Core `CosmosSessionTokensTest+CosmosNonSharedSessionTokenTests` tests to fail because the
-expected `CosmosException` is never thrown:
-
-- `UseSessionTokens_uses_session_tokens`
-- `Read_item_session_not_found_throws_CosmosException`
-
-## Stand-alone repro (no EF Core)
-
-`Program.csproj`:
-
-```xml
-
-
- Exe
- net8.0
-
-
-
-
-
-```
-
-`Program.cs`:
-
-```csharp
-using System.Net.Http;
-using Microsoft.Azure.Cosmos;
-
-const string Endpoint = "https://localhost:8081";
-const string Key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
-
-var options = new CosmosClientOptions
-{
- ConnectionMode = ConnectionMode.Gateway,
- ConsistencyLevel = ConsistencyLevel.Session,
- HttpClientFactory = () => new HttpClient(new HttpClientHandler
- {
- ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
- })
-};
-
-using var client = new CosmosClient(Endpoint, Key, options);
-var db = (await client.CreateDatabaseIfNotExistsAsync("ReproDb")).Database;
-var container = (await db.CreateContainerIfNotExistsAsync(
- new ContainerProperties("ReproFuture", "/pk"))).Container;
-
-// Write an item to obtain a valid session token in the form ":<...>#".
-var write = await container.UpsertItemAsync(
- new { id = "1", pk = "1" }, new PartitionKey("1"));
-var valid = write.Headers.Session; // e.g. "0:0#5"
-
-// Build a token in the same range but with LSN = int.MaxValue.
-var hash = valid.IndexOf('#');
-var future = valid.Substring(0, hash + 1) + int.MaxValue;
-Console.WriteLine($"valid = {valid}");
-Console.WriteLine($"future = {future}");
-
-try
-{
- var result = await container.ReadItemAsync(
- "1", new PartitionKey("1"),
- new ItemRequestOptions { SessionToken = future });
-
- // Linux emulator: prints "200 OK" (ignored the unsatisfiable session token).
- Console.WriteLine($"Read succeeded with StatusCode={result.StatusCode}");
-}
-catch (CosmosException ex)
-{
- // Real Cosmos / Windows emulator: 404 with the standard "read session is not available" message.
- Console.WriteLine($"Status={ex.StatusCode} SubStatus={ex.SubStatusCode}");
- Console.WriteLine(ex.ResponseBody);
-}
-```
-
-Expected output (real Cosmos / Windows emulator):
-
-```
-Status=NotFound SubStatus=1002
-... The read session is not available for the input session token. ...
-```
-
-Actual output on the Linux emulator:
-
-```
-Read succeeded with StatusCode=OK
-```
-
-## Suggested fix
-
-The emulator should honor the session-token contract: when a client sends a session token whose LSN is greater than the
-current max global LSN for the target partition, the request must either (a) wait for the session to become available
-up to the configured timeout, or (b) return `404 NotFound` with sub-status `1002`
-(`The read session is not available for the input session token.`), as the real service does. Returning `200 OK` while
-ignoring the session token breaks session-consistency guarantees for clients that rely on causal reads.
diff --git a/docs/cosmos-emulator-issues/03-failed-write-advances-session-token.md b/docs/cosmos-emulator-issues/03-failed-write-advances-session-token.md
deleted file mode 100644
index b72006333f9..00000000000
--- a/docs/cosmos-emulator-issues/03-failed-write-advances-session-token.md
+++ /dev/null
@@ -1,125 +0,0 @@
-# Linux Cosmos DB emulator returns an extra session-token LSN across multi-context concurrency sequences
-
-Tracking issue: [Azure/azure-cosmos-db-emulator-docker#291](https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/291)
-
-## Summary
-
-While running EF Core's `CosmosSessionTokensTest+CosmosNonSharedSessionTokenTests.Optimistic_concurrency_precondition_failure_updates_session_token`
-against the **Linux** emulator we observe that the set of session tokens EF Core sees across a multi-client
-optimistic-concurrency sequence contains **one extra LSN value** that the same sequence does not produce against the
-**Windows** emulator or the real Cosmos DB service.
-
-Specifically, EF Core's `CompositeSessionToken` (which accumulates every distinct session-token response value the SDK
-hands it during a logical sequence) collects:
-
-| Environment | Tokens observed (joined) |
-| --- | --- |
-| Real Cosmos / Windows emulator | `0:0#51,0:0#52,0:0#0,0:0#54` |
-| Linux emulator | `0:0#51,0:0#52,0:0#0,0:0#53,0:0#54` |
-
-Note the extra `0:0#53` slipping in between `0:0#0` and `0:0#54`. That extra LSN was returned in a response (most likely
-to a *failed* write — either the 412 from the stale-ETag `Replace`, or the 412/404 from the subsequent stale-ETag
-`Delete`) that should not have advanced the partition's session LSN.
-
-The failing assertions are:
-
-- `Optimistic_concurrency_precondition_failure_updates_session_token(autoTransactionBehavior: Always)`
-- `Optimistic_concurrency_precondition_failure_updates_session_token(autoTransactionBehavior: Never)`
-
-## Stand-alone repro (no EF Core)
-
-The simplest deterministic repro is a two-"client" sequence:
-client A creates a document, client B reads then replaces it, then client A
-tries to replace using its now-stale ETag (and again to delete with the stale ETag). Capture the session token
-returned in each response and look for an LSN that only appears under the Linux emulator.
-
-`Program.csproj`:
-
-```xml
-
-
- Exe
- net8.0
-
-
-
-
-
-```
-
-`Program.cs`:
-
-```csharp
-using System.Net.Http;
-using Microsoft.Azure.Cosmos;
-
-const string Endpoint = "https://localhost:8081";
-const string Key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
-
-var options = new CosmosClientOptions
-{
- ConnectionMode = ConnectionMode.Gateway,
- ConsistencyLevel = ConsistencyLevel.Session,
- HttpClientFactory = () => new HttpClient(new HttpClientHandler
- {
- ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
- })
-};
-
-using var client = new CosmosClient(Endpoint, Key, options);
-var db = (await client.CreateDatabaseIfNotExistsAsync("ReproDb")).Database;
-var c = (await db.CreateContainerIfNotExistsAsync(new ContainerProperties("ReproEtag", "/pk"))).Container;
-
-void Log(string label, string? session) => Console.WriteLine($"{label,-12} session={session}");
-
-// Client A creates
-var a1 = await c.CreateItemAsync(new { id = "1", pk = "1", v = 1 }, new PartitionKey("1"));
-var staleEtag = a1.ETag;
-Log("A create", a1.Headers.Session);
-
-// Client B reads and updates
-var b1 = await c.ReadItemAsync("1", new PartitionKey("1"));
-Log("B read", b1.Headers.Session);
-
-var b2 = await c.ReplaceItemAsync(new { id = "1", pk = "1", v = 2 }, "1", new PartitionKey("1"),
- new ItemRequestOptions { IfMatchEtag = b1.ETag });
-Log("B replace", b2.Headers.Session);
-var latestSuccessfulSession = b2.Headers.Session;
-
-// Client A tries to replace using its stale ETag -> 412 (FAILED write)
-try
-{
- await c.ReplaceItemAsync(new { id = "1", pk = "1", v = 3 }, "1", new PartitionKey("1"),
- new ItemRequestOptions { IfMatchEtag = staleEtag });
-}
-catch (CosmosException ex)
-{
- Log("A replace*", ex.Headers?.Session);
- // EXPECTED (real Cosmos / Windows emulator): same as 'B replace' (failed write does not advance LSN).
- // OBSERVED (Linux emulator): may differ by 1 LSN.
-}
-
-// Client A tries to delete using its stale ETag -> 412 (FAILED write)
-try
-{
- await c.DeleteItemAsync("1", new PartitionKey("1"),
- new ItemRequestOptions { IfMatchEtag = staleEtag });
-}
-catch (CosmosException ex)
-{
- Log("A delete*", ex.Headers?.Session);
-}
-```
-
-Expected on the real service: every session token observed after `B replace` equals `latestSuccessfulSession` until the
-next successful write. Observed on the Linux emulator: at least one of the failed-write responses returns a session
-token with a higher LSN than `latestSuccessfulSession`, which is then accumulated by EF Core's `CompositeSessionToken`
-and causes the equality assertion to fail.
-
-## Suggested fix
-
-A failed write (412 PreconditionFailed, 404 NotFound on Delete/Replace, etc.) must not bump the partition's session
-LSN, and the response header `x-ms-session-token` must reflect the last committed LSN — matching the behaviour of the
-real Cosmos DB service and the Windows emulator. Otherwise, callers relying on session-token equality across
-contexts to verify causality and detect concurrency outcomes (as the EF Core Cosmos provider does) see spurious
-extra session tokens.
diff --git a/test/EFCore.Cosmos.FunctionalTests/CosmosSessionTokensTest.cs b/test/EFCore.Cosmos.FunctionalTests/CosmosSessionTokensTest.cs
index cbd20b6bea1..3732b7a4d51 100644
--- a/test/EFCore.Cosmos.FunctionalTests/CosmosSessionTokensTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/CosmosSessionTokensTest.cs
@@ -598,8 +598,6 @@ protected Test2Context()
}
}
- // https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/291 (Session tokens not properly tracked)
- [CosmosCondition(CosmosCondition.IsNotLinuxEmulator)]
public class CosmosNonSharedSessionTokenTests(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture
{
protected override ITestStoreFactory NonSharedTestStoreFactory
@@ -609,7 +607,9 @@ protected override ITestStoreFactory NonSharedTestStoreFactory
protected override TestStore CreateTestStore() => CosmosTestStore.Create(NonSharedStoreName, (cfg) => cfg.SessionTokenManagementMode(Cosmos.Infrastructure.SessionTokenManagementMode.SemiAutomatic));
+ // https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/322
[ConditionalFact]
+ [CosmosCondition(CosmosCondition.IsNotLinuxEmulator)]
public virtual async Task UseSessionTokens_uses_session_tokens()
{
var contextFactory = await InitializeNonSharedTest();
@@ -653,7 +653,9 @@ public virtual async Task ReadItem_does_not_exist_returns_null()
Assert.Null(result);
}
+ // https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/322
[ConditionalFact]
+ [CosmosCondition(CosmosCondition.IsNotLinuxEmulator)]
public virtual async Task Read_item_session_not_found_throws_CosmosException()
{
var contextFactory = await InitializeNonSharedTest();
@@ -740,7 +742,9 @@ public virtual async Task Pooled_context_clears_SessionTokenStorage()
Assert.True(_sessionTokenStorage.ClearCalled);
}
+ // https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/319
[ConditionalTheory]
+ [CosmosCondition(CosmosCondition.IsNotLinuxEmulator)]
[InlineData(AutoTransactionBehavior.Never)]
[InlineData(AutoTransactionBehavior.Always)]
public virtual async Task Optimistic_concurrency_precondition_failure_updates_session_token(AutoTransactionBehavior autoTransactionBehavior)