diff --git a/build/tasks/npmcopy.js b/build/tasks/npmcopy.js
index 9bd3eea4..47c943ab 100644
--- a/build/tasks/npmcopy.js
+++ b/build/tasks/npmcopy.js
@@ -8,7 +8,10 @@ const files = {
 
 	"qunit/qunit.js": "qunit/qunit/qunit.js",
 	"qunit/qunit.css": "qunit/qunit/qunit.css",
-	"qunit/LICENSE.txt": "qunit/LICENSE.txt"
+	"qunit/LICENSE.txt": "qunit/LICENSE.txt",
+
+	"sinon/sinon.js": "sinon/pkg/sinon.js",
+	"sinon/LICENSE.txt": "sinon/LICENSE"
 };
 
 async function npmcopy() {
diff --git a/eslint.config.js b/eslint.config.js
index a83d79b0..6faa17df 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -95,6 +95,7 @@ export default [
 				...globals.browser,
 				jQuery: false,
 				QUnit: false,
+				sinon: false,
 				url: false,
 				expectWarning: false,
 				expectNoWarning: false,
diff --git a/package-lock.json b/package-lock.json
index 82ef8940..67deac94 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
 	"name": "jquery-migrate",
-	"version": "3.5.1-pre",
+	"version": "3.5.3-pre",
 	"lockfileVersion": 3,
 	"requires": true,
 	"packages": {
 		"": {
 			"name": "jquery-migrate",
-			"version": "3.5.1-pre",
+			"version": "3.5.3-pre",
 			"license": "MIT",
 			"devDependencies": {
 				"@types/selenium-webdriver": "4.1.22",
@@ -30,6 +30,7 @@
 				"qunit": "2.21.0",
 				"rollup": "4.18.0",
 				"selenium-webdriver": "4.21.0",
+				"sinon": "9.2.4",
 				"uglify-js": "3.9.4",
 				"yargs": "17.7.2"
 			},
@@ -430,6 +431,45 @@
 				"win32"
 			]
 		},
+		"node_modules/@sinonjs/commons": {
+			"version": "1.8.6",
+			"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
+			"integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==",
+			"dev": true,
+			"license": "BSD-3-Clause",
+			"dependencies": {
+				"type-detect": "4.0.8"
+			}
+		},
+		"node_modules/@sinonjs/fake-timers": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
+			"integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
+			"dev": true,
+			"license": "BSD-3-Clause",
+			"dependencies": {
+				"@sinonjs/commons": "^1.7.0"
+			}
+		},
+		"node_modules/@sinonjs/samsam": {
+			"version": "5.3.1",
+			"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz",
+			"integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==",
+			"dev": true,
+			"license": "BSD-3-Clause",
+			"dependencies": {
+				"@sinonjs/commons": "^1.6.0",
+				"lodash.get": "^4.4.2",
+				"type-detect": "^4.0.8"
+			}
+		},
+		"node_modules/@sinonjs/text-encoding": {
+			"version": "0.7.3",
+			"resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz",
+			"integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==",
+			"dev": true,
+			"license": "(Unlicense OR Apache-2.0)"
+		},
 		"node_modules/@types/body-parser": {
 			"version": "1.19.5",
 			"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -3032,6 +3072,13 @@
 				"setimmediate": "^1.0.5"
 			}
 		},
+		"node_modules/just-extend": {
+			"version": "4.2.1",
+			"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz",
+			"integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==",
+			"dev": true,
+			"license": "MIT"
+		},
 		"node_modules/keyv": {
 			"version": "4.5.4",
 			"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -3078,6 +3125,13 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
+		"node_modules/lodash.get": {
+			"version": "4.4.2",
+			"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+			"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
+			"dev": true,
+			"license": "MIT"
+		},
 		"node_modules/lodash.merge": {
 			"version": "4.6.2",
 			"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3195,6 +3249,37 @@
 				"node": ">= 0.6"
 			}
 		},
+		"node_modules/nise": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz",
+			"integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==",
+			"dev": true,
+			"license": "BSD-3-Clause",
+			"dependencies": {
+				"@sinonjs/commons": "^1.7.0",
+				"@sinonjs/fake-timers": "^6.0.0",
+				"@sinonjs/text-encoding": "^0.7.1",
+				"just-extend": "^4.0.2",
+				"path-to-regexp": "^1.7.0"
+			}
+		},
+		"node_modules/nise/node_modules/isarray": {
+			"version": "0.0.1",
+			"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+			"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/nise/node_modules/path-to-regexp": {
+			"version": "1.9.0",
+			"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz",
+			"integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"isarray": "0.0.1"
+			}
+		},
 		"node_modules/node-watch": {
 			"version": "0.7.3",
 			"resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz",
@@ -3999,6 +4084,49 @@
 				"url": "https://github.com/sponsors/ljharb"
 			}
 		},
+		"node_modules/sinon": {
+			"version": "9.2.4",
+			"resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz",
+			"integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==",
+			"deprecated": "16.1.1",
+			"dev": true,
+			"license": "BSD-3-Clause",
+			"dependencies": {
+				"@sinonjs/commons": "^1.8.1",
+				"@sinonjs/fake-timers": "^6.0.1",
+				"@sinonjs/samsam": "^5.3.1",
+				"diff": "^4.0.2",
+				"nise": "^4.0.4",
+				"supports-color": "^7.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/sinon"
+			}
+		},
+		"node_modules/sinon/node_modules/diff": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+			"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+			"dev": true,
+			"license": "BSD-3-Clause",
+			"engines": {
+				"node": ">=0.3.1"
+			}
+		},
+		"node_modules/sinon/node_modules/supports-color": {
+			"version": "7.2.0",
+			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+			"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"has-flag": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
 		"node_modules/spawnback": {
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/spawnback/-/spawnback-1.0.1.tgz",
@@ -4289,6 +4417,16 @@
 				"node": ">= 0.8.0"
 			}
 		},
+		"node_modules/type-detect": {
+			"version": "4.0.8",
+			"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+			"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=4"
+			}
+		},
 		"node_modules/type-fest": {
 			"version": "0.20.2",
 			"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
diff --git a/package.json b/package.json
index 2e1b73e7..a4cca443 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
 		"qunit": "2.21.0",
 		"rollup": "4.18.0",
 		"selenium-webdriver": "4.21.0",
+		"sinon": "9.2.4",
 		"uglify-js": "3.9.4",
 		"yargs": "17.7.2"
 	},
diff --git a/src/jquery/deferred.js b/src/jquery/deferred.js
index 30268adb..7f036efe 100644
--- a/src/jquery/deferred.js
+++ b/src/jquery/deferred.js
@@ -1,9 +1,15 @@
-import { migratePatchFunc, migratePatchAndWarnFunc } from "../main.js";
+import {
+	migratePatchFunc,
+	migratePatchAndWarnFunc,
+	migrateWarn
+} from "../main.js";
+import { jQueryVersionSince } from "../compareVersions.js";
 
 // Support jQuery slim which excludes the deferred module in jQuery 4.0+
 if ( jQuery.Deferred ) {
 
-var oldDeferred = jQuery.Deferred,
+var unpatchedGetStackHookValue,
+	oldDeferred = jQuery.Deferred,
 	tuples = [
 
 		// Action, add listener, callbacks, .then handlers, final state
@@ -63,4 +69,39 @@ migratePatchFunc( jQuery, "Deferred", function( func ) {
 // Preserve handler of uncaught exceptions in promise chains
 jQuery.Deferred.exceptionHook = oldDeferred.exceptionHook;
 
+// Preserve the optional hook to record the error, if defined
+jQuery.Deferred.getErrorHook = oldDeferred.getErrorHook;
+
+// We want to mirror jQuery.Deferred.getErrorHook here, so we cannot use
+// existing Migrate utils.
+Object.defineProperty( jQuery.Deferred, "getStackHook", {
+	configurable: true,
+	enumerable: true,
+	get: function() {
+		if ( jQuery.migrateIsPatchEnabled( "deferred-getStackHook" ) ) {
+
+			// jQuery 3.x checks `getStackHook` if `getErrorHook` missing;
+			// don't warn on the getter there.
+			if ( jQueryVersionSince( "4.0.0" ) ) {
+				migrateWarn( "deferred-getStackHook",
+					"jQuery.Deferred.getStackHook is deprecated; " +
+					"use jQuery.Deferred.getErrorHook" );
+			}
+			return jQuery.Deferred.getErrorHook;
+		} else {
+			return unpatchedGetStackHookValue;
+		}
+	},
+	set: function( newValue ) {
+		if ( jQuery.migrateIsPatchEnabled( "deferred-getStackHook" ) ) {
+			migrateWarn( "deferred-getStackHook",
+				"jQuery.Deferred.getStackHook is deprecated; " +
+					"use jQuery.Deferred.getErrorHook" );
+			jQuery.Deferred.getErrorHook = newValue;
+		} else {
+			unpatchedGetStackHookValue = newValue;
+		}
+	}
+} );
+
 }
diff --git a/test/index.html b/test/index.html
index a6aa9cfe..22f840c0 100644
--- a/test/index.html
+++ b/test/index.html
@@ -10,6 +10,7 @@
 	<!-- QUnit -->
 	<link rel="stylesheet" href="../external/qunit/qunit.css" media="screen">
 	<script src="../external/qunit/qunit.js"></script>
+	<script src="../external/sinon/sinon.js"></script>
 
 	<!-- A promise polyfill -->
 	<script src="../external/npo/npo.js"></script>
diff --git a/test/unit/jquery/deferred.js b/test/unit/jquery/deferred.js
index e602e075..0c91dfd0 100644
--- a/test/unit/jquery/deferred.js
+++ b/test/unit/jquery/deferred.js
@@ -1,7 +1,15 @@
 // Support jQuery slim which excludes the deferred module in jQuery 4.0+
 if ( jQuery.Deferred ) {
 
-QUnit.module( "deferred" );
+QUnit.module( "deferred", {
+	beforeEach: function() {
+		this.sandbox = sinon.createSandbox();
+	},
+	afterEach: function() {
+		this.sandbox.restore();
+		jQuery.Deferred.getErrorHook = jQuery.Deferred.getStackHook = undefined;
+	}
+} );
 
 QUnit.test( "jQuery.Deferred.exceptionHook", function( assert ) {
 	assert.expect( 1 );
@@ -10,6 +18,238 @@ QUnit.test( "jQuery.Deferred.exceptionHook", function( assert ) {
 	assert.ok( typeof jQuery.Deferred.exceptionHook === "function", "hook is present" );
 } );
 
+QUnit.test( "jQuery.Deferred.getStackHook - getter", function( assert ) {
+	assert.expect( 5 );
+
+	var exceptionHookSpy,
+		done = assert.async();
+
+	// Source: https://github.com/dmethvin/jquery-deferred-reporter
+	function getErrorHook() {
+
+		// Throw an error as IE doesn't capture `stack` of non-thrown ones.
+		try {
+			throw new Error( "Test exception in jQuery.Deferred" );
+		} catch ( err ) {
+			return err;
+		}
+	}
+
+	jQuery.Deferred.getErrorHook = getErrorHook;
+
+	exceptionHookSpy = this.sandbox.spy( jQuery.Deferred, "exceptionHook" );
+
+	expectWarning( assert, "jQuery.Deferred.getStackHook - getter",
+
+		// The getter only warns in jQuery 4+ as jQuery 3.x reads it internally.
+		jQueryVersionSince( "4.0.0" ) ? 1 : 0,
+		function() {
+		assert.strictEqual( jQuery.Deferred.getStackHook, jQuery.Deferred.getErrorHook,
+			"getStackHook mirrors getErrorHook (getter)" );
+	} );
+
+	expectNoWarning( assert, "asyncHook reported in jQuery.Deferred.exceptionHook", function() {
+		jQuery
+			.when()
+			.then( function() {
+				throw new ReferenceError( "Test ReferenceError" );
+			} )
+			.catch( function() {
+				var asyncError = exceptionHookSpy.lastCall.args[ 1 ];
+				assert.ok( asyncError instanceof Error,
+					"Error passed to exceptionHook (instance)" );
+				assert.strictEqual( asyncError.message, "Test exception in jQuery.Deferred",
+					"Error passed to exceptionHook (message)" );
+				done();
+			} );
+	} );
+} );
+
+QUnit.test( "jQuery.Deferred.getStackHook - getter, no getErrorHook", function( assert ) {
+	assert.expect( 1 );
+
+	var done = assert.async();
+
+	expectNoWarning( assert, "No Migrate warning in a regular `then`", function() {
+		jQuery
+			.when()
+			.then( function() {
+				done();
+			} );
+	} );
+} );
+
+QUnit.test( "jQuery.Deferred.getStackHook - setter", function( assert ) {
+	assert.expect( 5 );
+
+	var exceptionHookSpy,
+		done = assert.async();
+
+	exceptionHookSpy = this.sandbox.spy( jQuery.Deferred, "exceptionHook" );
+
+	expectWarning( assert, "jQuery.Deferred.getStackHook - setter", 1, function() {
+		var mockFn = function() {};
+		jQuery.Deferred.getStackHook = mockFn;
+		assert.strictEqual( jQuery.Deferred.getErrorHook, mockFn,
+			"getStackHook mirrors getErrorHook (setter)" );
+	} );
+
+	expectWarning( assert, "asyncHook from jQuery.Deferred.getStackHook reported",
+			1, function() {
+		jQuery.Deferred.getStackHook = function() {
+
+			// Throw an error as IE doesn't capture `stack` of non-thrown ones.
+			try {
+				throw new SyntaxError( "Different exception in jQuery.Deferred" );
+			} catch ( err ) {
+				return err;
+			}
+		};
+
+		jQuery
+			.when()
+			.then( function() {
+				throw new ReferenceError( "Test ReferenceError" );
+			} )
+			.catch( function() {
+				var asyncError = exceptionHookSpy.lastCall.args[ 1 ];
+				assert.ok( asyncError instanceof SyntaxError,
+					"Error passed to exceptionHook (instance)" );
+				assert.strictEqual( asyncError.message, "Different exception in jQuery.Deferred",
+					"Error passed to exceptionHook (message)" );
+
+				done();
+			} );
+	} );
+} );
+
+QUnit.test( "jQuery.Deferred.getStackHook - disabled patch, getter", function( assert ) {
+	assert.expect( 5 );
+
+	var exceptionHookSpy,
+		done = assert.async();
+
+	// Source: https://github.com/dmethvin/jquery-deferred-reporter
+	function getErrorHook() {
+
+		// Throw an error as IE doesn't capture `stack` of non-thrown ones.
+		try {
+			throw new Error( "Test exception in jQuery.Deferred" );
+		} catch ( err ) {
+			return err;
+		}
+	}
+
+	jQuery.migrateDisablePatches( "deferred-getStackHook" );
+
+	jQuery.Deferred.getErrorHook = getErrorHook;
+
+	exceptionHookSpy = this.sandbox.spy( jQuery.Deferred, "exceptionHook" );
+
+	expectNoWarning( assert, "jQuery.Deferred.getStackHook - getter", function() {
+		assert.strictEqual( jQuery.Deferred.getStackHook, undefined,
+			"getStackHook does not mirror getErrorHook (getter)" );
+	} );
+
+	expectNoWarning( assert, "asyncHook reported in jQuery.Deferred.exceptionHook", function() {
+		jQuery
+			.when()
+			.then( function() {
+				throw new ReferenceError( "Test ReferenceError" );
+			} )
+			.catch( function() {
+				var asyncError = exceptionHookSpy.lastCall.args[ 1 ];
+				assert.ok( asyncError instanceof Error,
+					"Error passed to exceptionHook (instance)" );
+				assert.strictEqual( asyncError.message, "Test exception in jQuery.Deferred",
+					"Error passed to exceptionHook (message)" );
+				done();
+			} );
+	} );
+} );
+
+QUnit.test( "jQuery.Deferred.getStackHook - disabled patch, setter", function( assert ) {
+	assert.expect( jQueryVersionSince( "4.0.0" ) ? 4 : 5 );
+
+	var exceptionHookSpy,
+		done = assert.async();
+
+	// Source: https://github.com/dmethvin/jquery-deferred-reporter
+	function getErrorHook() {
+
+		// Throw an error as IE doesn't capture `stack` of non-thrown ones.
+		try {
+			throw new Error( "Test exception in jQuery.Deferred" );
+		} catch ( err ) {
+			return err;
+		}
+	}
+
+	jQuery.migrateDisablePatches( "deferred-getStackHook" );
+
+	jQuery.Deferred.getErrorHook = getErrorHook;
+
+	exceptionHookSpy = this.sandbox.spy( jQuery.Deferred, "exceptionHook" );
+
+	expectNoWarning( assert, "jQuery.Deferred.getStackHook - setter", function() {
+		var mockFn = function() {};
+		jQuery.Deferred.getStackHook = mockFn;
+		assert.strictEqual( jQuery.Deferred.getErrorHook, getErrorHook,
+			"getStackHook does not mirror getErrorHook (setter)" );
+	} );
+
+	expectNoWarning( assert, "asyncHook from jQuery.Deferred.getStackHook reported", function() {
+			jQuery.Deferred.getErrorHook = undefined;
+			jQuery.Deferred.getStackHook = function() {
+
+			// Throw an error as IE doesn't capture `stack` of non-thrown ones.
+			try {
+				throw new SyntaxError( "Different exception in jQuery.Deferred" );
+			} catch ( err ) {
+				return err;
+			}
+		};
+
+		jQuery
+			.when()
+			.then( function() {
+				throw new ReferenceError( "Test ReferenceError" );
+			} )
+			.catch( function() {
+				var asyncError = exceptionHookSpy.lastCall.args[ 1 ];
+
+				if ( jQueryVersionSince( "4.0.0" ) ) {
+					assert.strictEqual( asyncError, undefined,
+						"Error not passed to exceptionHook" );
+				} else {
+					assert.ok( asyncError instanceof Error,
+						"Error passed to exceptionHook (instance)" );
+					assert.strictEqual( asyncError.message,
+						"Different exception in jQuery.Deferred",
+						"Error passed to exceptionHook (message)" );
+				}
+
+				done();
+			} );
+	} );
+} );
+
+QUnit.test( "jQuery.Deferred.getStackHook - disabled patch, getter + setter interaction",
+		function( assert ) {
+	assert.expect( 3 );
+
+	jQuery.migrateDisablePatches( "deferred-getStackHook" );
+
+	expectNoWarning( assert, "jQuery.Deferred.getStackHook - setter & getter", function() {
+		var mockFn = function() {};
+		assert.strictEqual( jQuery.Deferred.getStackHook, undefined,
+			"getStackHook is `undefined` by default" );
+		jQuery.Deferred.getStackHook = mockFn;
+		assert.strictEqual( jQuery.Deferred.getStackHook, mockFn,
+			"getStackHook getter reports what the setter set" );
+	} );
+} );
+
 QUnit.test( ".pipe() warnings", function( assert ) {
 	assert.expect( 4 );
 
diff --git a/warnings.md b/warnings.md
index 57c657c8..2d6a7dc7 100644
--- a/warnings.md
+++ b/warnings.md
@@ -296,3 +296,9 @@ See jQuery-ui [commit](https://github.com/jquery/jquery-ui/commit/c0093b599fcd58
 **Cause:** `jQuery.ajax` calls with `dataType: 'json'` with a provided callback are automatically converted by jQuery to JSONP requests unless the options also specify `jsonp: false`. Auto-promoting JSON requests to JSONP introduces a security risk as the developer may be unaware they're not just downloading data but executing code from a remote domain. This auto-promoting behavior is deprecated and will be removed in jQuery 4.0.0.
 
 **Solution:** To trigger a JSONP request, specify the `dataType: "jsonp"` option.
+
+### \[deferred-getStackHook\] JQMIGRATE: jQuery.Deferred.getStackHook is deprecated; use jQuery.Deferred.getErrorHook
+
+**Cause:** `jQuery.Deferred.getStackHook` was originally created to pass the stack trace from before an async barrier to report when a user error (like calling a non-existing function) causes a promise to be rejected. However, passing a stack trace doesn't take source maps into account, so we started advising to pass the whole error object. To make it clearer, we also renamed the API to `jQuery.Deferred.getErrorHook`. The legacy alias will be removed in jQuery 4.0.0
+
+**Solution:** Rename all usage of `jQuery.Deferred.getStackHook` to `jQuery.Deferred.getErrorHook`. If you previously assigned a function returning an error stack to `jQuery.Deferred.getStackHook` or `jQuery.Deferred.getErrorHook`, change it to return a full error object.