diff --git a/common/changes/@microsoft/rush/kenrick-fix-update-shrinkwrap-packageExtensions_2024-09-06-03-55.json b/common/changes/@microsoft/rush/kenrick-fix-update-shrinkwrap-packageExtensions_2024-09-06-03-55.json
new file mode 100644
index 00000000000..deaa6a64bf8
--- /dev/null
+++ b/common/changes/@microsoft/rush/kenrick-fix-update-shrinkwrap-packageExtensions_2024-09-06-03-55.json
@@ -0,0 +1,10 @@
+{
+  "changes": [
+    {
+      "packageName": "@microsoft/rush",
+      "comment": "Always update shrinkwrap when globalPackageExtensions has been changed",
+      "type": "none"
+    }
+  ],
+  "packageName": "@microsoft/rush"
+}
\ No newline at end of file
diff --git a/common/changes/@rushstack/node-core-library/kenrick-fix-update-shrinkwrap-packageExtensions_2024-09-06-03-55.json b/common/changes/@rushstack/node-core-library/kenrick-fix-update-shrinkwrap-packageExtensions_2024-09-06-03-55.json
new file mode 100644
index 00000000000..32953c4e722
--- /dev/null
+++ b/common/changes/@rushstack/node-core-library/kenrick-fix-update-shrinkwrap-packageExtensions_2024-09-06-03-55.json
@@ -0,0 +1,10 @@
+{
+  "changes": [
+    {
+      "packageName": "@rushstack/node-core-library",
+      "comment": "Add Sort.sortKeys for sorting keys in an object",
+      "type": "minor"
+    }
+  ],
+  "packageName": "@rushstack/node-core-library"
+}
\ No newline at end of file
diff --git a/common/reviews/api/node-core-library.api.md b/common/reviews/api/node-core-library.api.md
index 4c7786d3668..9ff8037e54a 100644
--- a/common/reviews/api/node-core-library.api.md
+++ b/common/reviews/api/node-core-library.api.md
@@ -831,6 +831,10 @@ export class Sort {
     static isSorted<T>(collection: Iterable<T>, comparer?: (x: any, y: any) => number): boolean;
     static isSortedBy<T>(collection: Iterable<T>, keySelector: (element: T) => any, comparer?: (x: any, y: any) => number): boolean;
     static sortBy<T>(array: T[], keySelector: (element: T) => any, comparer?: (x: any, y: any) => number): void;
+    static sortKeys<T extends Partial<Record<string, unknown>> | unknown[]>(object: T, { deep, compare }?: {
+        deep?: boolean;
+        compare?: (x: string, y: string) => number;
+    }): T;
     static sortMapKeys<K, V>(map: Map<K, V>, keyComparer?: (x: K, y: K) => number): void;
     static sortSet<T>(set: Set<T>, comparer?: (x: T, y: T) => number): void;
     static sortSetBy<T>(set: Set<T>, keySelector: (element: T) => any, keyComparer?: (x: T, y: T) => number): void;
diff --git a/libraries/node-core-library/src/Sort.ts b/libraries/node-core-library/src/Sort.ts
index 77ff843c67a..a0cd15df047 100644
--- a/libraries/node-core-library/src/Sort.ts
+++ b/libraries/node-core-library/src/Sort.ts
@@ -231,4 +231,97 @@ export class Sort {
       set.add(item);
     }
   }
+
+  /**
+   * Sort the keys given in an object
+   *
+   * @example
+   *
+   * ```ts
+   * console.log(Sort.sortKeys({ c: 3, b: 2, a: 1 })); // { a: 1, b: 2, c: 3}
+   * ```
+   */
+  public static sortKeys<T extends Partial<Record<string, unknown>> | unknown[]>(
+    object: T,
+    { deep, compare }: { deep?: boolean; compare?: (x: string, y: string) => number } = {
+      deep: false,
+      compare: Sort.compareByValue
+    }
+  ): T {
+    function isPlainObject(obj: unknown): obj is object {
+      return obj !== null && typeof obj === 'object';
+    }
+    if (!isPlainObject(object) && !Array.isArray(object)) {
+      throw new TypeError(`Expected object or array`);
+    }
+
+    /**
+     * `cache`: object | array --\> index in cacheResults
+     * `cacheResults`: the actual sorted object | array.
+     * It's stored in different variable to allow reference while mutating the object, to handle circular objects
+     */
+    const cache: WeakMap<Partial<Record<string, unknown>> | unknown[], number> = new WeakMap();
+    const cacheResults: Array<Partial<Record<string, unknown>> | unknown[]> = [];
+
+    function innerSortArray(arr: unknown[]): unknown[] {
+      let cacheResultIndex: undefined | number = cache.get(arr);
+      if (cacheResultIndex !== undefined) {
+        return cacheResults[cacheResultIndex] as unknown[];
+      }
+      const result: unknown[] = [];
+      cacheResultIndex = cacheResults.push(result) - 1;
+      cache.set(arr, cacheResultIndex);
+      if (deep) {
+        result.push(
+          ...arr.map((entry) => {
+            if (Array.isArray(entry)) {
+              return innerSortArray(entry);
+            } else if (isPlainObject(entry)) {
+              return innerSortKeys(entry);
+            }
+            return entry;
+          })
+        );
+      } else {
+        result.push(...arr);
+      }
+
+      return result;
+    }
+    function innerSortKeys(obj: Partial<Record<string, unknown>>): Partial<Record<string, unknown>> {
+      let cacheResultIndex: undefined | number = cache.get(obj);
+      if (cacheResultIndex !== undefined) {
+        return cacheResults[cacheResultIndex] as Partial<Record<string, unknown>>;
+      }
+      const result: Partial<Record<string, unknown>> = {};
+      const keys: string[] = Object.keys(obj).sort(compare);
+
+      cacheResultIndex = cacheResults.push(result) - 1;
+      cache.set(obj, cacheResultIndex);
+
+      for (const key of keys) {
+        const value: unknown = obj[key];
+        let newValue: unknown;
+        if (deep) {
+          if (Array.isArray(value)) {
+            newValue = innerSortArray(value);
+          } else if (isPlainObject(value)) {
+            newValue = innerSortKeys(value);
+          } else {
+            newValue = value;
+          }
+        } else {
+          newValue = value;
+        }
+        Object.defineProperty(result, key, {
+          ...Object.getOwnPropertyDescriptor(obj, key),
+          value: newValue
+        });
+      }
+
+      return result;
+    }
+
+    return Array.isArray(object) ? (innerSortArray(object) as T) : (innerSortKeys(object) as T);
+  }
 }
diff --git a/libraries/node-core-library/src/test/Sort.test.ts b/libraries/node-core-library/src/test/Sort.test.ts
index 997e5f344c1..17cb8124c0e 100644
--- a/libraries/node-core-library/src/test/Sort.test.ts
+++ b/libraries/node-core-library/src/test/Sort.test.ts
@@ -69,3 +69,182 @@ test('Sort.sortSet', () => {
   Sort.sortSet(set);
   expect(Array.from(set)).toEqual(['aardvark', 'goose', 'zebra']);
 });
+
+describe('Sort.sortKeys', () => {
+  // Test cases from https://github.com/sindresorhus/sort-keys/blob/v4.2.0/test.js
+  function deepEqualInOrder(
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    actual: Partial<Record<string, any>>,
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    expected: Partial<Record<string, any>>
+  ): void {
+    expect(actual).toEqual(expected);
+
+    const seen = new Set();
+
+    function assertSameKeysInOrder(
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      object1: Partial<Record<string, any>>,
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      object2: Partial<Record<string, any>>
+    ): void {
+      // This function assumes the objects given are already deep equal.
+
+      if (seen.has(object1) && seen.has(object2)) {
+        return;
+      }
+
+      seen.add(object1);
+      seen.add(object2);
+
+      if (Array.isArray(object1)) {
+        for (const index of object1.keys()) {
+          assertSameKeysInOrder(object1[index], object2[index]);
+        }
+      } else if (typeof object1 === 'object') {
+        const keys1 = Object.keys(object1);
+        const keys2 = Object.keys(object2);
+        expect(keys1).toEqual(keys2);
+        for (const index of keys1.keys()) {
+          assertSameKeysInOrder(object1[keys1[index]], object2[keys2[index]]);
+        }
+      }
+    }
+
+    assertSameKeysInOrder(actual, expected);
+  }
+
+  test('sort the keys of an object', () => {
+    deepEqualInOrder(Sort.sortKeys({ c: 0, a: 0, b: 0 }), { a: 0, b: 0, c: 0 });
+  });
+
+  test('custom compare function', () => {
+    const compare: (a: string, b: string) => number = (a: string, b: string) => b.localeCompare(a);
+    deepEqualInOrder(Sort.sortKeys({ c: 0, a: 0, b: 0 }, { compare }), { c: 0, b: 0, a: 0 });
+  });
+
+  test('deep option', () => {
+    deepEqualInOrder(Sort.sortKeys({ c: { c: 0, a: 0, b: 0 }, a: 0, b: 0 }, { deep: true }), {
+      a: 0,
+      b: 0,
+      c: { a: 0, b: 0, c: 0 }
+    });
+
+    expect(() => {
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      const object: Partial<Record<string, any>> = { a: 0 };
+      object.circular = object;
+      Sort.sortKeys(object, { deep: true });
+    }).not.toThrow();
+
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const object: Partial<Record<string, any>> = { z: 0 };
+    object.circular = object;
+    const sortedObject = Sort.sortKeys(object, { deep: true });
+
+    expect(sortedObject).toBe(sortedObject.circular);
+    expect(Object.keys(sortedObject)).toEqual(['circular', 'z']);
+
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const object1: Partial<Record<string, any>> = { b: 0 };
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const object2: Partial<Record<string, any>> = { d: 0 };
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const object3: Partial<Record<string, any>> = { a: [{ b: 0 }] };
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const object4: Partial<Record<string, any>> = { a: [{ d: 0 }] };
+
+    object1.a = object2;
+    object2.c = object1;
+    object3.a[0].a = object4.a[0];
+    object4.a[0].c = object3.a[0];
+
+    expect(() => {
+      Sort.sortKeys(object1, { deep: true });
+      Sort.sortKeys(object2, { deep: true });
+      Sort.sortKeys(object3, { deep: true });
+      Sort.sortKeys(object4, { deep: true });
+    }).not.toThrow();
+
+    const sorted = Sort.sortKeys(object1, { deep: true });
+    const deepSorted = Sort.sortKeys(object3, { deep: true });
+
+    expect(sorted).toBe(sorted.a.c);
+    deepEqualInOrder(deepSorted.a[0], deepSorted.a[0].a.c);
+    expect(Object.keys(sorted)).toStrictEqual(['a', 'b']);
+    expect(Object.keys(deepSorted.a[0])).toStrictEqual(['a', 'b']);
+    deepEqualInOrder(
+      Sort.sortKeys({ c: { c: 0, a: 0, b: 0 }, a: 0, b: 0, z: [9, 8, 7, 6, 5] }, { deep: true }),
+      { a: 0, b: 0, c: { a: 0, b: 0, c: 0 }, z: [9, 8, 7, 6, 5] }
+    );
+    expect(Object.keys(Sort.sortKeys({ a: [{ b: 0, a: 0 }] }, { deep: true }).a[0])).toEqual(['a', 'b']);
+  });
+
+  test('deep arrays', () => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const object: Partial<Record<string, any>> = {
+      b: 0,
+      a: [{ b: 0, a: 0 }, [{ b: 0, a: 0 }]]
+    };
+    object.a.push(object);
+    object.a[1].push(object.a[1]);
+
+    expect(() => {
+      Sort.sortKeys(object, { deep: true });
+    }).not.toThrow();
+
+    const sorted = Sort.sortKeys(object, { deep: true });
+    // Cannot use .toBe() as Jest will encounter https://github.com/jestjs/jest/issues/10577
+    expect(sorted.a[2] === sorted).toBeTruthy();
+    expect(sorted.a[1][1] === sorted.a[1]).toBeTruthy();
+    expect(Object.keys(sorted)).toEqual(['a', 'b']);
+    expect(Object.keys(sorted.a[0])).toEqual(['a', 'b']);
+    expect(Object.keys(sorted.a[1][0])).toEqual(['a', 'b']);
+  });
+
+  test('top-level array', () => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const array: Array<any> = [
+      { b: 0, a: 0 },
+      { c: 0, d: 0 }
+    ];
+    const sorted = Sort.sortKeys(array);
+    expect(sorted).not.toBe(array);
+    expect(sorted[0]).toBe(array[0]);
+    expect(sorted[1]).toBe(array[1]);
+
+    const deepSorted = Sort.sortKeys(array, { deep: true });
+    expect(deepSorted).not.toBe(array);
+    expect(deepSorted[0]).not.toBe(array[0]);
+    expect(deepSorted[1]).not.toBe(array[1]);
+    expect(Object.keys(deepSorted[0])).toEqual(['a', 'b']);
+    expect(Object.keys(deepSorted[1])).toEqual(['c', 'd']);
+  });
+
+  test('keeps property descriptors intact', () => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const descriptors: Partial<Record<string, any>> = {
+      b: {
+        value: 1,
+        configurable: true,
+        enumerable: true,
+        writable: false
+      },
+      a: {
+        value: 2,
+        configurable: false,
+        enumerable: true,
+        writable: true
+      }
+    };
+
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const object: Partial<Record<string, any>> = {};
+    Object.defineProperties(object, descriptors);
+
+    const sorted = Sort.sortKeys(object);
+
+    deepEqualInOrder(sorted, { a: 2, b: 1 });
+    expect(Object.getOwnPropertyDescriptors(sorted)).toEqual(descriptors);
+  });
+});
diff --git a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts
index bdcb2615f4d..f582da32357 100644
--- a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts
+++ b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts
@@ -10,8 +10,10 @@ import {
   AlreadyReportedError,
   Async,
   type IDependenciesMetaTable,
-  Path
+  Path,
+  Sort
 } from '@rushstack/node-core-library';
+import { createHash } from 'crypto';
 
 import { BaseInstallManager } from '../base/BaseInstallManager';
 import type { IInstallManagerOptions } from '../base/BaseInstallManagerTypes';
@@ -378,6 +380,18 @@ export class WorkspaceInstallManager extends BaseInstallManager {
       shrinkwrapIsUpToDate = false;
     }
 
+    // Check if packageExtensionsChecksum matches globalPackageExtension's hash
+    const packageExtensionsChecksum: string | undefined = this._getPackageExtensionChecksum(
+      this.rushConfiguration.pnpmOptions.globalPackageExtensions
+    );
+    const packageExtensionsChecksumAreEqual: boolean =
+      packageExtensionsChecksum === shrinkwrapFile?.packageExtensionsChecksum;
+
+    if (!packageExtensionsChecksumAreEqual) {
+      shrinkwrapWarnings.push("The package extension hash doesn't match the current shrinkwrap.");
+      shrinkwrapIsUpToDate = false;
+    }
+
     // Write the common package.json
     InstallHelpers.generateCommonPackageJson(this.rushConfiguration, subspace, undefined);
 
@@ -388,6 +402,22 @@ export class WorkspaceInstallManager extends BaseInstallManager {
     return { shrinkwrapIsUpToDate, shrinkwrapWarnings };
   }
 
+  private _getPackageExtensionChecksum(
+    packageExtensions: Record<string, unknown> | undefined
+  ): string | undefined {
+    const packageExtensionsChecksum: string | undefined =
+      Object.keys(packageExtensions ?? {}).length === 0
+        ? undefined
+        : createObjectChecksum(packageExtensions!);
+
+    function createObjectChecksum(obj: Record<string, unknown>): string {
+      const s: string = JSON.stringify(Sort.sortKeys(obj, { deep: true }));
+      return createHash('md5').update(s).digest('hex');
+    }
+
+    return packageExtensionsChecksum;
+  }
+
   protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace): boolean {
     if (!super.canSkipInstall(lastModifiedDate, subspace)) {
       return false;
diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts
index 52fbd234f81..3860200b737 100644
--- a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts
+++ b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts
@@ -148,6 +148,8 @@ export interface IPnpmShrinkwrapYaml {
   specifiers: Record<string, string>;
   /** The list of override version number for dependencies */
   overrides?: { [dependency: string]: string };
+  /** The checksum of package extensions fields for extending dependencies */
+  packageExtensionsChecksum?: string;
 }
 
 export interface ILoadFromFileOptions {
@@ -275,6 +277,7 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
   public readonly specifiers: ReadonlyMap<string, string>;
   public readonly packages: ReadonlyMap<string, IPnpmShrinkwrapDependencyYaml>;
   public readonly overrides: ReadonlyMap<string, string>;
+  public readonly packageExtensionsChecksum: undefined | string;
 
   private readonly _shrinkwrapJson: IPnpmShrinkwrapYaml;
   private readonly _integrities: Map<string, Map<string, string>>;
@@ -304,6 +307,7 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
     this.specifiers = new Map(Object.entries(shrinkwrapJson.specifiers || {}));
     this.packages = new Map(Object.entries(shrinkwrapJson.packages || {}));
     this.overrides = new Map(Object.entries(shrinkwrapJson.overrides || {}));
+    this.packageExtensionsChecksum = shrinkwrapJson.packageExtensionsChecksum;
 
     // Importers only exist in workspaces
     this.isWorkspaceCompatible = this.importers.size > 0;