{"record":{"id":"4ad835f6d5746fae","repo":"dotnet/orleans","slug":"cosmos-db-condition-not-satisfied-graintype-0","errorCode":null,"errorMessage":"Cosmos DB condition not satisfied. GrainType: {0}, GrainId: {1}, TableName: {2}, StoredETag: {3}, CurrentETag: {4}","messagePattern":"Cosmos DB condition not satisfied\\. GrainType: (.+?), GrainId: (.+?), TableName: (.+?), StoredETag: (.+?), CurrentETag: (.+?)","errorType":"exception","errorClass":"CosmosConditionNotSatisfiedException","httpStatus":null,"severity":"error","filePath":"src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs","lineNumber":154,"sourceCode":"            }\n            else\n            {\n                var requestOptions = new ItemRequestOptions { IfMatchEtag = grainState.ETag };\n                response = await _executor.ExecuteOperation(\n                    static args =>\n                    {\n                        var (self, entity, pk, requestOptions) = args;\n                        return self._container.ReplaceItemAsync(entity, entity.Id, pk, requestOptions);\n                    },\n                    (this, entity, pk, requestOptions)).ConfigureAwait(false);\n            }\n\n            grainState.ETag = response.Resource.ETag;\n            grainState.RecordExists = true;\n        }\n        catch (CosmosException ex) when (ex.StatusCode is HttpStatusCode.PreconditionFailed or HttpStatusCode.Conflict or HttpStatusCode.NotFound)\n        {\n            throw new CosmosConditionNotSatisfiedException(grainType, grainId, _options.ContainerName, \"Unknown\", grainState.ETag);\n        }\n        catch (Exception exc)\n        {\n            LogErrorWritingState(exc, grainType, id);\n            WrappedException.CreateAndRethrow(exc);\n            throw;\n        }\n    }\n\n    public async Task ClearStateAsync<T>(string grainType, GrainId grainId, IGrainState<T> grainState)\n    {\n        var (id, partitionKey) = await _documentIdProvider.GetDocumentIdentifiers(grainType, grainId);\n\n        LogTraceClearingState(grainType, id, grainId, grainState.ETag, _options.DeleteStateOnClear, _options.ContainerName, partitionKey);\n\n        var pk = new PartitionKey(partitionKey);\n        var requestOptions = new ItemRequestOptions { IfMatchEtag = grainState.ETag };\n        try","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs#L136-L172","documentation":"Thrown by CosmosGrainStorage.WriteStateAsync when a CosmosException occurs with PreconditionFailed (412), Conflict (409), or NotFound (404) status. This is an optimistic ETag concurrency violation during a ReplaceItemAsync — the stored document's ETag does not match the ETag the client sent, indicating a concurrent modification or that the document was deleted.","triggerScenarios":"Grain reads state (ETag A), a concurrent write changes the document (ETag B), then this grain attempts ReplaceItemAsync with the stale ETag A. Cosmos DB rejects with HTTP 412. Also triggered by 409 Conflict or 404 NotFound if the document was simultaneously modified or removed.","commonSituations":"Multiple activations of the same grain across silos writing concurrently. Reentrant operations that trigger nested state writes. Race conditions in multi-silo or multi-datacenter deployments. Document deleted between read and write.","solutions":["Catch CosmosConditionNotSatisfiedException and re-read/retry the operation.","Ensure single-activation semantics for the grain to avoid concurrent writes from different silos.","Reduce write contention by batching updates within a single grain method call.","Configure the Cosmos executor retry policy for transient conflicts."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await grain.WriteStateAsync(); }\ncatch (CosmosConditionNotSatisfiedException ex)\n{\n    logger.LogWarning(ex, \"Cosmos ETag conflict on {GrainType} {GrainId}.\", ex.GrainType, ex.GrainId);\n    // Re-read state and retry the write\n    await grain.ReadStateAsync();\n    await grain.WriteStateAsync();\n}","preventionTips":["Ensure single-activation semantics to prevent concurrent writes to the same grain.","Minimize the read-modify-write window in grain methods to reduce ETag conflicts.","Monitor Cosmos 412/409 rates as a contention indicator.","Let the Orleans runtime's built-in retry handle transient conflicts when possible."],"tags":["cosmos-db","optimistic-concurrency","etag","conflict","concurrency","orleans"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}