diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index 6fe22b704778985b0973fa27bd3001ecd8a719f1..61834b9a9d29d5709bba4019b56a7055243ea23b 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -146,6 +146,8 @@ So for example you could elect to automerge all (passing) `devDependencies` only
 
 Important: Renovate won't automerge on GitHub if a PR has a negative review outstanding.
 
+Note: on Azure there can be a delay between a PR being set as completed by RenovateBot, and Azure merging the PR / finishing it's tasks. RenovateBot will try to delay until Azure is in the expected state, however if it takes too long it will continue. In some cases this can result in a dependency not being merged, and a fresh PR being created for the dependency.
+
 ## automergeComment
 
 Use this only if you configure `automergeType="pr-comment"`.
diff --git a/lib/platform/azure/index.spec.ts b/lib/platform/azure/index.spec.ts
index a0325054ec73714322f4c95fa153f93431ac7614..5151d362a4b2ba9f900025fc51a81fdad2490282 100644
--- a/lib/platform/azure/index.spec.ts
+++ b/lib/platform/azure/index.spec.ts
@@ -4,6 +4,7 @@ import {
   GitStatusState,
   PullRequestStatus,
 } from 'azure-devops-node-api/interfaces/GitInterfaces';
+import { logger as _logger } from '../../logger';
 import { BranchStatus, PrState } from '../../types';
 import * as _git from '../../util/git';
 import * as _hostRules from '../../util/host-rules';
@@ -15,6 +16,7 @@ describe('platform/azure', () => {
   let azureApi: jest.Mocked<typeof import('./azure-got-wrapper')>;
   let azureHelper: jest.Mocked<typeof import('./azure-helper')>;
   let git: jest.Mocked<typeof _git>;
+  let logger: jest.Mocked<typeof _logger>;
   beforeEach(async () => {
     // reset module
     jest.resetModules();
@@ -22,11 +24,14 @@ describe('platform/azure', () => {
     jest.mock('./azure-helper');
     jest.mock('../../util/git');
     jest.mock('../../util/host-rules');
+    jest.mock('../../logger');
+    jest.mock('delay');
     hostRules = require('../../util/host-rules');
     require('../../util/sanitize').sanitize = jest.fn((input) => input);
     azure = await import('.');
     azureApi = require('./azure-got-wrapper');
     azureHelper = require('./azure-helper');
+    logger = (await import('../../logger')).logger as never;
     git = require('../../util/git');
     git.branchExists.mockReturnValue(true);
     git.isBranchStale.mockResolvedValue(false);
@@ -1012,7 +1017,9 @@ describe('platform/azure', () => {
       const pullRequestIdMock = 12345;
       const branchNameMock = 'test';
       const lastMergeSourceCommitMock = { commitId: 'abcd1234' };
-      const updatePullRequestMock = jest.fn();
+      const updatePullRequestMock = jest.fn(() => ({
+        status: 3,
+      }));
       azureApi.gitApi.mockImplementationOnce(
         () =>
           ({
@@ -1090,6 +1097,68 @@ describe('platform/azure', () => {
 
       expect(azureHelper.getMergeMethod).toHaveBeenCalledTimes(1);
     });
+
+    it('should refetch the PR if the update response has not yet been set to completed', async () => {
+      await initRepo({ repository: 'some/repo' });
+      const pullRequestIdMock = 12345;
+      const branchNameMock = 'test';
+      const lastMergeSourceCommitMock = { commitId: 'abcd1234' };
+      const getPullRequestByIdMock = jest.fn(() => ({
+        lastMergeSourceCommit: lastMergeSourceCommitMock,
+        targetRefName: 'refs/heads/ding',
+        status: 3,
+      }));
+      azureApi.gitApi.mockImplementationOnce(
+        () =>
+          ({
+            getPullRequestById: getPullRequestByIdMock,
+            updatePullRequest: jest.fn(() => ({
+              status: 1,
+            })),
+          } as any)
+      );
+      azureHelper.getMergeMethod = jest
+        .fn()
+        .mockReturnValue(GitPullRequestMergeStrategy.Squash);
+
+      const res = await azure.mergePr(pullRequestIdMock, branchNameMock);
+
+      expect(getPullRequestByIdMock).toHaveBeenCalledTimes(2);
+      expect(res).toBe(true);
+    });
+
+    it('should log a warning after retrying if the PR has still not yet been set to completed', async () => {
+      await initRepo({ repository: 'some/repo' });
+      const pullRequestIdMock = 12345;
+      const branchNameMock = 'test';
+      const lastMergeSourceCommitMock = { commitId: 'abcd1234' };
+      const expectedNumRetries = 5;
+      const getPullRequestByIdMock = jest.fn(() => ({
+        lastMergeSourceCommit: lastMergeSourceCommitMock,
+        targetRefName: 'refs/heads/ding',
+        status: 1,
+      }));
+      azureApi.gitApi.mockImplementationOnce(
+        () =>
+          ({
+            getPullRequestById: getPullRequestByIdMock,
+            updatePullRequest: jest.fn(() => ({
+              status: 1,
+            })),
+          } as any)
+      );
+      azureHelper.getMergeMethod = jest
+        .fn()
+        .mockReturnValue(GitPullRequestMergeStrategy.Squash);
+
+      const res = await azure.mergePr(pullRequestIdMock, branchNameMock);
+
+      expect(getPullRequestByIdMock).toHaveBeenCalledTimes(
+        expectedNumRetries + 1
+      );
+      expect(logger.warn).toHaveBeenCalled();
+      expect(res).toBe(true);
+    });
   });
 
   describe('getVulnerabilityAlerts()', () => {
diff --git a/lib/platform/azure/index.ts b/lib/platform/azure/index.ts
index 738c5f171d056112821b5c47e091492f376308c4..4b11286c7c5499e8c80443be591d9f0c6940b34d 100644
--- a/lib/platform/azure/index.ts
+++ b/lib/platform/azure/index.ts
@@ -7,6 +7,7 @@ import {
   GitStatusState,
   PullRequestStatus,
 } from 'azure-devops-node-api/interfaces/GitInterfaces';
+import delay from 'delay';
 import { REPOSITORY_EMPTY } from '../../constants/error-messages';
 import { PLATFORM_TYPE_AZURE } from '../../constants/platforms';
 import { logger } from '../../logger';
@@ -587,10 +588,7 @@ export async function mergePr(
   logger.debug(`mergePr(${pullRequestId}, ${branchName})`);
   const azureApiGit = await azureApi.gitApi();
 
-  const pr = await azureApiGit.getPullRequestById(
-    pullRequestId,
-    config.project
-  );
+  let pr = await azureApiGit.getPullRequestById(pullRequestId, config.project);
 
   const mergeMethod =
     config.mergeMethods[pr.targetRefName] ??
@@ -620,11 +618,35 @@ export async function mergePr(
   );
 
   try {
-    await azureApiGit.updatePullRequest(
+    const response = await azureApiGit.updatePullRequest(
       objToUpdate,
       config.repoId,
       pullRequestId
     );
+
+    let retries = 0;
+    let isClosed = response.status === PullRequestStatus.Completed;
+    while (!isClosed && retries < 5) {
+      retries += 1;
+      const sleepMs = retries * 1000;
+      logger.trace(
+        { pullRequestId, status: pr.status, retries },
+        `Updated PR to closed status but change has not taken effect yet. Retrying...`
+      );
+
+      await delay(sleepMs);
+      pr = await azureApiGit.getPullRequestById(pullRequestId, config.project);
+      isClosed = pr.status === PullRequestStatus.Completed;
+    }
+
+    if (!isClosed) {
+      logger.warn(
+        { pullRequestId, status: pr.status },
+        `Expected PR to have status ${
+          PullRequestStatus[PullRequestStatus.Completed]
+        }, however it is ${PullRequestStatus[pr.status]}.`
+      );
+    }
     return true;
   } catch (err) {
     logger.debug({ err }, 'Failed to set the PR as completed.');