Commit 91c4edc3011 for woocommerce

commit 91c4edc3011a4d9b90441d3af940912c8956a613
Author: Ismael Martín Alabarce <info@ismaeld.com>
Date:   Tue Aug 11 09:02:40 2026 +0200

    Fix stale task routes in WooCommerce Admin (#67362)

    * Fix stale task routes in WooCommerce Admin

    * Add stale task route changelog entry

    ---------

    Co-authored-by: Sam Najian <dev@najian.info>

diff --git a/plugins/woocommerce/changelog/fix-wooplug-3391-stale-task-redirect b/plugins/woocommerce/changelog/fix-wooplug-3391-stale-task-redirect
new file mode 100644
index 00000000000..83efe7c387b
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-wooplug-3391-stale-task-redirect
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Redirect missing WooCommerce Admin task routes to Home instead of leaving a blank screen.
diff --git a/plugins/woocommerce/client/admin/client/task-lists/task-lists.tsx b/plugins/woocommerce/client/admin/client/task-lists/task-lists.tsx
index 21efb55468e..5f2bda48bc3 100644
--- a/plugins/woocommerce/client/admin/client/task-lists/task-lists.tsx
+++ b/plugins/woocommerce/client/admin/client/task-lists/task-lists.tsx
@@ -4,9 +4,10 @@
 import { __ } from '@wordpress/i18n';
 import { MenuGroup, MenuItem } from '@wordpress/components';
 import { check } from '@wordpress/icons';
-import { Fragment } from '@wordpress/element';
+import { Fragment, useEffect } from '@wordpress/element';
 import { useDispatch, useSelect } from '@wordpress/data';
 import { onboardingStore, TaskListType, TaskType } from '@woocommerce/data';
+import { getHistory, getNewPath } from '@woocommerce/navigation';
 import { recordEvent } from '@woocommerce/tracks';

 /**
@@ -79,6 +80,12 @@ export const TaskLists = ( { query }: TaskListsProps ) => {

 	const currentTask = getCurrentTask();

+	useEffect( () => {
+		if ( task && ! currentTask && ! isResolving ) {
+			getHistory().replace( getNewPath( {}, '/', {} ) );
+		}
+	}, [ currentTask, isResolving, task ] );
+
 	if ( task && ! currentTask ) {
 		return null;
 	}
diff --git a/plugins/woocommerce/client/admin/client/task-lists/test/tasks.test.tsx b/plugins/woocommerce/client/admin/client/task-lists/test/tasks.test.tsx
index f7247e69815..d2dd57d3175 100644
--- a/plugins/woocommerce/client/admin/client/task-lists/test/tasks.test.tsx
+++ b/plugins/woocommerce/client/admin/client/task-lists/test/tasks.test.tsx
@@ -4,6 +4,7 @@
 import { render, act, cleanup, waitFor } from '@testing-library/react';
 import { useDispatch, useSelect } from '@wordpress/data';
 import { recordEvent } from '@woocommerce/tracks';
+import { getHistory, getNewPath } from '@woocommerce/navigation';
 import userEvent from '@testing-library/user-event';

 /**
@@ -28,6 +29,11 @@ jest.mock( '@wordpress/data', () => {

 jest.mock( '@woocommerce/explat' );
 jest.mock( '@woocommerce/tracks' );
+jest.mock( '@woocommerce/navigation', () => ( {
+	...jest.requireActual( '@woocommerce/navigation' ),
+	getHistory: jest.fn(),
+	getNewPath: jest.fn().mockReturnValue( 'home-path' ),
+} ) );

 jest.mock( '../components/task-list', () => ( {
 	TaskList: ( { id }: TaskListProps ) => <div>task-list:{ id }</div>,
@@ -56,8 +62,12 @@ jest.mock( '~/activity-panel/display-options', () => ( {
 describe( 'Task', () => {
 	const hideTaskList = jest.fn();
 	const updateOptions = jest.fn();
+	const historyReplace = jest.fn();
 	beforeEach( () => {
 		jest.clearAllMocks();
+		( getHistory as jest.Mock ).mockReturnValue( {
+			replace: historyReplace,
+		} );
 		( useDispatch as jest.Mock ).mockImplementation( () => ( {
 			hideTaskList,
 			updateOptions,
@@ -102,9 +112,10 @@ describe( 'Task', () => {
 			</div>
 		);
 		expect( queryByText( 'task:main-task-1' ) ).toBeInTheDocument();
+		expect( historyReplace ).not.toHaveBeenCalled();
 	} );

-	it( 'should not render anything if query has task, but task does not exist', () => {
+	it( 'should redirect home if query has task, but task does not exist', async () => {
 		const { queryByText } = render(
 			<div>
 				<TaskLists query={ { task: 'main-task-random' } } />
@@ -114,6 +125,23 @@ describe( 'Task', () => {
 			queryByText( 'task:main-task-random' )
 		).not.toBeInTheDocument();
 		expect( queryByText( 'task-list:main' ) ).not.toBeInTheDocument();
+		await waitFor( () => {
+			expect( historyReplace ).toHaveBeenCalledWith( 'home-path' );
+		} );
+		expect( getNewPath ).toHaveBeenCalledWith( {}, '/', {} );
+	} );
+
+	it( 'should not redirect if task lists are still resolving', () => {
+		( useSelect as jest.Mock ).mockImplementation( () => ( {
+			isResolving: true,
+			taskLists: [],
+		} ) );
+		render(
+			<div>
+				<TaskLists query={ { task: 'main-task-random' } } />
+			</div>
+		);
+		expect( historyReplace ).not.toHaveBeenCalled();
 	} );

 	it( 'should render the placeholder if isResolving is true', () => {