diff --git a/package.json b/package.json
index bb11eddcf..441a70b12 100644
--- a/package.json
+++ b/package.json
@@ -79,11 +79,15 @@
 	"devDependencies": {
 		"@ampproject/remapping": "^2.3.0",
 		"@types/cross-spawn": "^6.0.6",
+		"@types/glob-parent": "^5.1.3",
+		"@types/is-glob": "^4.0.4",
 		"@types/node": "^20.14.9",
+		"@types/normalize-path": "^3.0.2",
+		"@types/picomatch": "^3.0.1",
 		"@types/split2": "^4.2.3",
 		"append-transform": "^2.0.0",
 		"cachedir": "^2.4.0",
-		"chokidar": "^3.6.0",
+		"chokidar": "^4.0.1",
 		"clean-pkg-json": "^1.2.0",
 		"cleye": "^1.3.2",
 		"cross-spawn": "^7.0.3",
@@ -92,13 +96,17 @@
 		"fs-fixture": "^2.4.0",
 		"fs-require": "^1.6.0",
 		"get-node": "^15.0.1",
+		"glob-parent": "^6.0.2",
+		"is-glob": "^4.0.3",
 		"kolorist": "^1.8.0",
 		"lintroll": "^1.8.1",
 		"magic-string": "^0.30.10",
 		"manten": "^1.3.0",
 		"memfs": "^4.9.3",
 		"node-pty": "^1.0.0",
+		"normalize-path": "^3.0.0",
 		"outdent": "^0.8.0",
+		"picomatch": "^4.0.2",
 		"pkgroll": "^2.4.1",
 		"proxyquire": "^2.1.3",
 		"split2": "^4.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e62720dce..996771833 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -25,9 +25,21 @@ importers:
       '@types/cross-spawn':
         specifier: ^6.0.6
         version: 6.0.6
+      '@types/glob-parent':
+        specifier: ^5.1.3
+        version: 5.1.3
+      '@types/is-glob':
+        specifier: ^4.0.4
+        version: 4.0.4
       '@types/node':
         specifier: ^20.14.9
         version: 20.14.9
+      '@types/normalize-path':
+        specifier: ^3.0.2
+        version: 3.0.2
+      '@types/picomatch':
+        specifier: ^3.0.1
+        version: 3.0.1
       '@types/split2':
         specifier: ^4.2.3
         version: 4.2.3
@@ -38,8 +50,8 @@ importers:
         specifier: ^2.4.0
         version: 2.4.0
       chokidar:
-        specifier: ^3.6.0
-        version: 3.6.0
+        specifier: ^4.0.1
+        version: 4.0.1
       clean-pkg-json:
         specifier: ^1.2.0
         version: 1.2.0
@@ -64,6 +76,12 @@ importers:
       get-node:
         specifier: ^15.0.1
         version: 15.0.1
+      glob-parent:
+        specifier: ^6.0.2
+        version: 6.0.2
+      is-glob:
+        specifier: ^4.0.3
+        version: 4.0.3
       kolorist:
         specifier: ^1.8.0
         version: 1.8.0
@@ -82,9 +100,15 @@ importers:
       node-pty:
         specifier: ^1.0.0
         version: 1.0.0
+      normalize-path:
+        specifier: ^3.0.0
+        version: 3.0.0
       outdent:
         specifier: ^0.8.0
         version: 0.8.0
+      picomatch:
+        specifier: ^4.0.2
+        version: 4.0.2
       pkgroll:
         specifier: ^2.4.1
         version: 2.4.1(typescript@5.5.2)
@@ -956,9 +980,15 @@ packages:
   '@types/estree@1.0.5':
     resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
 
+  '@types/glob-parent@5.1.3':
+    resolution: {integrity: sha512-p+NciRH8TRvrgISOCQ55CP+lktMmDpOXsp4spULIIz0L4aJ6G9zFX+N0UZ2xulmJRgaQLRxXIp4xHdL6YOQjDg==}
+
   '@types/http-cache-semantics@4.0.4':
     resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
 
+  '@types/is-glob@4.0.4':
+    resolution: {integrity: sha512-3mFBtIPQ0TQetKRDe94g8YrxJZxdMillMGegyv6zRBXvq4peRRhf2wLZ/Dl53emtTsC29dQQBwYvovS20yXpiQ==}
+
   '@types/istanbul-lib-coverage@2.0.6':
     resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
 
@@ -992,6 +1022,12 @@ packages:
   '@types/normalize-package-data@2.4.4':
     resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
 
+  '@types/normalize-path@3.0.2':
+    resolution: {integrity: sha512-DO++toKYPaFn0Z8hQ7Tx+3iT9t77IJo/nDiqTXilgEP+kPNIYdpS9kh3fXuc53ugqwp9pxC1PVjCpV1tQDyqMA==}
+
+  '@types/picomatch@3.0.1':
+    resolution: {integrity: sha512-1MRgzpzY0hOp9pW/kLRxeQhUWwil6gnrUYd3oEpeYBqp/FexhaCPv3F8LsYr47gtUU45fO2cm1dbwkSrHEo8Uw==}
+
   '@types/prop-types@15.7.12':
     resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
 
@@ -1335,8 +1371,8 @@ packages:
   brace-expansion@2.0.1:
     resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
 
-  braces@3.0.2:
-    resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+  braces@3.0.3:
+    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
     engines: {node: '>=8'}
 
   browserslist@4.23.0:
@@ -1408,6 +1444,10 @@ packages:
     resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
     engines: {node: '>= 8.10.0'}
 
+  chokidar@4.0.1:
+    resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==}
+    engines: {node: '>= 14.16.0'}
+
   ci-info@3.8.0:
     resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==}
     engines: {node: '>=8'}
@@ -1878,8 +1918,8 @@ packages:
     resolution: {integrity: sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==}
     engines: {node: '>=0.10.0'}
 
-  fill-range@7.0.1:
-    resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+  fill-range@7.1.1:
+    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
     engines: {node: '>=8'}
 
   filter-obj@5.1.0:
@@ -1994,6 +2034,7 @@ packages:
 
   glob@7.2.3:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Glob versions prior to v9 are no longer supported
 
   global-cache-dir@6.0.0:
     resolution: {integrity: sha512-UOwXU6ulg3VQsSyKf0QAVcW4EFq3hFehFHV/ne76iQ9FAw4ZpXHXsmw8AwUueGI13y4apVML/Pb+njilLn/RCw==}
@@ -2108,6 +2149,7 @@ packages:
 
   inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
 
   inherits@2.0.4:
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@@ -2477,8 +2519,12 @@ packages:
   micromark@2.11.4:
     resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==}
 
-  micromatch@4.0.5:
-    resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
+  micromatch@4.0.7:
+    resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
+    engines: {node: '>=8.6'}
+
+  micromatch@4.0.8:
+    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
     engines: {node: '>=8.6'}
 
   mimic-fn@4.0.0:
@@ -2894,6 +2940,10 @@ packages:
     resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
     engines: {node: '>=8.10.0'}
 
+  readdirp@4.0.1:
+    resolution: {integrity: sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==}
+    engines: {node: '>= 14.16.0'}
+
   refa@0.12.1:
     resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
     engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -2949,6 +2999,7 @@ packages:
 
   rimraf@3.0.2:
     resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+    deprecated: Rimraf versions prior to v4 are no longer supported
     hasBin: true
 
   rollup@4.17.1:
@@ -4140,8 +4191,12 @@ snapshots:
 
   '@types/estree@1.0.5': {}
 
+  '@types/glob-parent@5.1.3': {}
+
   '@types/http-cache-semantics@4.0.4': {}
 
+  '@types/is-glob@4.0.4': {}
+
   '@types/istanbul-lib-coverage@2.0.6': {}
 
   '@types/istanbul-lib-report@3.0.3':
@@ -4175,6 +4230,10 @@ snapshots:
 
   '@types/normalize-package-data@2.4.4': {}
 
+  '@types/normalize-path@3.0.2': {}
+
+  '@types/picomatch@3.0.1': {}
+
   '@types/prop-types@15.7.12':
     optional: true
 
@@ -4602,9 +4661,9 @@ snapshots:
     dependencies:
       balanced-match: 1.0.2
 
-  braces@3.0.2:
+  braces@3.0.3:
     dependencies:
-      fill-range: 7.0.1
+      fill-range: 7.1.1
 
   browserslist@4.23.0:
     dependencies:
@@ -4674,7 +4733,7 @@ snapshots:
   chokidar@3.6.0:
     dependencies:
       anymatch: 3.1.3
-      braces: 3.0.2
+      braces: 3.0.3
       glob-parent: 5.1.2
       is-binary-path: 2.1.0
       is-glob: 4.0.3
@@ -4683,6 +4742,10 @@ snapshots:
     optionalDependencies:
       fsevents: 2.3.3
 
+  chokidar@4.0.1:
+    dependencies:
+      readdirp: 4.0.1
+
   ci-info@3.8.0: {}
 
   ci-info@4.0.0: {}
@@ -5360,7 +5423,7 @@ snapshots:
       '@nodelib/fs.walk': 1.2.8
       glob-parent: 5.1.2
       merge2: 1.4.1
-      micromatch: 4.0.5
+      micromatch: 4.0.7
 
   fast-json-stable-stringify@2.1.0: {}
 
@@ -5396,7 +5459,7 @@ snapshots:
       is-object: 1.0.2
       merge-descriptors: 1.0.3
 
-  fill-range@7.0.1:
+  fill-range@7.1.1:
     dependencies:
       to-regex-range: 5.0.1
 
@@ -5847,7 +5910,7 @@ snapshots:
       '@types/stack-utils': 2.0.3
       chalk: 4.1.2
       graceful-fs: 4.2.11
-      micromatch: 4.0.5
+      micromatch: 4.0.8
       pretty-format: 29.7.0
       slash: 3.0.0
       stack-utils: 2.0.6
@@ -6046,9 +6109,14 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  micromatch@4.0.5:
+  micromatch@4.0.7:
     dependencies:
-      braces: 3.0.2
+      braces: 3.0.3
+      picomatch: 2.3.1
+
+  micromatch@4.0.8:
+    dependencies:
+      braces: 3.0.3
       picomatch: 2.3.1
 
   mimic-fn@4.0.0: {}
@@ -6443,6 +6511,8 @@ snapshots:
     dependencies:
       picomatch: 2.3.1
 
+  readdirp@4.0.1: {}
+
   refa@0.12.1:
     dependencies:
       '@eslint-community/regexpp': 4.11.0
@@ -6775,7 +6845,7 @@ snapshots:
       is-glob: 4.0.3
       jiti: 1.21.0
       lilconfig: 2.1.0
-      micromatch: 4.0.5
+      micromatch: 4.0.7
       normalize-path: 3.0.0
       object-hash: 3.0.0
       picocolors: 1.0.0
diff --git a/src/watch/index.ts b/src/watch/index.ts
index 826bce1c3..8629ade9e 100644
--- a/src/watch/index.ts
+++ b/src/watch/index.ts
@@ -4,7 +4,10 @@ import { constants as osConstants } from 'node:os';
 import path from 'node:path';
 import { command } from 'cleye';
 import { watch } from 'chokidar';
+import picomatch from 'picomatch';
 import { lightMagenta, lightGreen, yellow } from 'kolorist';
+import globParent from 'glob-parent';
+import isGlob from 'is-glob';
 import { run } from '../run.js';
 import {
 	removeArgvFlags,
@@ -15,6 +18,7 @@ import {
 	clearScreen,
 	debounce,
 	log,
+	resolveGlobPattern,
 } from './utils.js';
 
 const flags = {
@@ -81,6 +85,23 @@ export const watchCommand = command({
 
 	const server = await createIpcServer();
 
+	const isDefaultIgnore = picomatch([
+		// Hidden directories like .git
+		'**/.*/**',
+
+		// Hidden files (e.g. logs or temp files)
+		'**/.*',
+
+		// 3rd party packages
+		'**/{node_modules,bower_components,vendor}/**',
+	]);
+
+	const resolvedIncludes = options.include.map(resolveGlobPattern);
+	const isOptionsInclude = picomatch(resolvedIncludes);
+
+	const resolvedExcludes = options.exclude.map(resolveGlobPattern);
+	const isOptionsExclude = picomatch(resolvedExcludes);
+
 	server.on('data', (data) => {
 		// Collect run-time dependencies to watch
 		if (
@@ -97,7 +118,12 @@ export const watchCommand = command({
 					: data.path
 			);
 
-			if (path.isAbsolute(dependencyPath)) {
+			if (
+				path.isAbsolute(dependencyPath)
+				&& !isOptionsInclude(dependencyPath)
+				&& !isOptionsExclude(dependencyPath)
+				&& !isDefaultIgnore(dependencyPath)
+			) {
 				watcher.add(dependencyPath);
 			}
 		}
@@ -208,29 +234,39 @@ export const watchCommand = command({
 	 * As an alternative, we watch cwd and all run-time dependencies
 	 */
 	const watcher = watch(
-		[
-			...argv._,
-			...options.include,
-		],
+		argv._,
 		{
 			cwd: process.cwd(),
 			ignoreInitial: true,
-			ignored: [
-				// Hidden directories like .git
-				'**/.*/**',
-
-				// Hidden files (e.g. logs or temp files)
-				'**/.*',
-
-				// 3rd party packages
-				'**/{node_modules,bower_components,vendor}/**',
-
-				...options.exclude,
-			],
 			ignorePermissionErrors: true,
+			// ignore all files that are by default ignored or explicitly excluded
+			ignored: file => isDefaultIgnore(file) || isOptionsExclude(file),
 		},
 	).on('all', reRun);
 
+	if (resolvedIncludes.length > 0) {
+		const globParents = resolvedIncludes.map(pattern => (
+			isGlob(pattern)
+				? globParent(pattern)
+				: pattern
+		));
+
+		watch(globParents, {
+			cwd: process.cwd(),
+			ignoreInitial: true,
+			ignorePermissionErrors: true,
+			// ignore all files not in includes or explicitly excluded
+			// we need to make sure not to ignore directories otherwise chokidar won't check for it
+			ignored: file => (
+				!globParents.includes(file)
+				&& (
+					!isOptionsInclude(file)
+					|| isOptionsExclude(file)
+				)
+			),
+		}).on('all', reRun);
+	}
+
 	// On "Return" key
 	process.stdin.on('data', () => reRun('Return key'));
 });
diff --git a/src/watch/utils.ts b/src/watch/utils.ts
index 852f74aa8..cc542518a 100644
--- a/src/watch/utils.ts
+++ b/src/watch/utils.ts
@@ -1,4 +1,6 @@
+import path from 'node:path';
 import { gray, lightCyan } from 'kolorist';
+import normalizePath from 'normalize-path';
 
 const currentTime = () => (new Date()).toLocaleTimeString();
 
@@ -29,3 +31,10 @@ export const debounce = <T extends (this: unknown, ...args: any[]) => void>(
 		);
 	} as T;
 };
+
+export const resolveGlobPattern = (pattern: string): string => {
+	if (path.isAbsolute(pattern)) {
+		return normalizePath(pattern);
+	}
+	return normalizePath(path.join(process.cwd(), pattern));
+};
diff --git a/tests/specs/watch.ts b/tests/specs/watch.ts
index 8960a9f45..36abf2e5e 100644
--- a/tests/specs/watch.ts
+++ b/tests/specs/watch.ts
@@ -224,7 +224,7 @@ export default testSuite(async ({ describe }, { tsx }: NodeApis) => {
 		describe('include', ({ test }) => {
 			test('file path & glob', async () => {
 				const entryFile = 'index.js';
-				const fileA = 'file-a';
+				const fileA = '.file-a'; // Watches hidden files
 				const fileB = 'directory/file-b';
 				await using fixture = await createFixture({
 					[entryFile]: `
@@ -354,6 +354,80 @@ export default testSuite(async ({ describe }, { tsx }: NodeApis) => {
 
 				await tsxProcess;
 			}, 10_000);
+
+			test('with parent directory', async ({ onTestFail }) => {
+				const entryFile = 'process-directory/index.js';
+				const fileA = 'file-a.js';
+				const fileB = 'directory/file-b.js';
+				const depA = 'node_modules/a/index.js';
+
+				await using fixtureGlob = await createFixture({
+					[fileA]: 'export default "logA"',
+					[fileB]: 'export default "logB"',
+					[depA]: 'export default "logC"',
+					[entryFile]: `
+						import valueA from '../${fileA}'
+						import valueB from '../${fileB}'
+						import valueC from '../${depA}'
+						console.log(valueA, valueB, valueC)
+					`.trim(),
+				});
+
+				const tsxProcess = tsx(
+					[
+						'watch',
+						'--clear-screen=false',
+						`--ignore=../${fileA}`,
+						`--exclude=../${fileB}`,
+						'index.js',
+					],
+					fixtureGlob.getPath('process-directory'),
+				);
+
+				onTestFail(async () => {
+					// If timed out, force kill process
+					if (tsxProcess.exitCode === null) {
+						console.log('Force killing hanging process\n\n');
+						tsxProcess.kill();
+						console.log({
+							tsxProcess: await tsxProcess,
+						});
+					}
+				});
+
+				const negativeSignal = 'fail';
+
+				await expect(
+					processInteract(
+						tsxProcess.stdout!,
+						[
+							async (data) => {
+								if (data !== 'logA logB logC\n') {
+									return;
+								}
+
+								// These changes should not trigger a re-run
+								await Promise.all([
+									fixtureGlob.writeFile(fileA, `export default "${negativeSignal}"`),
+									fixtureGlob.writeFile(fileB, `export default "${negativeSignal}"`),
+									fixtureGlob.writeFile(depA, `export default "${negativeSignal}"`),
+								]);
+								return true;
+							},
+							(data) => {
+								if (data.includes(negativeSignal)) {
+									throw new Error('Unexpected re-run');
+								}
+							},
+						],
+						2000,
+					),
+				).rejects.toThrow('Timeout'); // Watch should not trigger
+
+				tsxProcess.kill();
+
+				await tsxProcess;
+			}, 10_000);
 		});
 	});
 });