diff --git a/docs/assets/images/new-expensify-rules.png b/docs/assets/images/new-expensify-rules.png
new file mode 100644
index 000000000000..5d37df0ed95b
Binary files /dev/null and b/docs/assets/images/new-expensify-rules.png differ
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 2ca42f2af036..2474f521872c 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -447,6 +447,9 @@ const ONYXKEYS = {
     /** Indicates whether we should record troubleshoot data or not */
     SHOULD_RECORD_TROUBLESHOOT_DATA: 'shouldRecordTroubleshootData',
 
+    /** Timestamp when troubleshoot recording was started (for auto-off after 10 minutes) */
+    TROUBLESHOOT_RECORDING_START_TIME: 'troubleshootRecordingStartTime',
+
     /** Indicates whether we should mask fragile user data while exporting onyx state or not */
     SHOULD_MASK_ONYX_STATE: 'shouldMaskOnyxState',
 
@@ -1283,6 +1286,7 @@ type OnyxValuesMapping = {
     [ONYXKEYS.LOGS]: OnyxTypes.CapturedLogs;
     [ONYXKEYS.SHOULD_STORE_LOGS]: boolean;
     [ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA]: boolean;
+    [ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME]: number | null;
     [ONYXKEYS.SHOULD_MASK_ONYX_STATE]: boolean;
     [ONYXKEYS.SHOULD_USE_STAGING_SERVER]: boolean;
     [ONYXKEYS.IS_DEBUG_MODE_ENABLED]: boolean;
diff --git a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
index 6f973e58f488..ca4b46d94a9a 100644
--- a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
+++ b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx
@@ -1,10 +1,8 @@
 import type JSZip from 'jszip';
 import type {RefObject} from 'react';
-import React, {useCallback, useEffect, useMemo, useState} from 'react';
+import React, {useEffect, useState} from 'react';
 import {Alert} from 'react-native';
-import RNFetchBlob from 'react-native-blob-util';
 import DeviceInfo from 'react-native-device-info';
-import {startProfiling, stopProfiling} from 'react-native-release-profiler';
 import Button from '@components/Button';
 import Switch from '@components/Switch';
 import TestToolRow from '@components/TestToolRow';
@@ -12,21 +10,16 @@ import Text from '@components/Text';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
 import useThemeStyles from '@hooks/useThemeStyles';
-import {disableLoggingAndFlushLogs, setShouldStoreLogs} from '@libs/actions/Console';
-import toggleProfileTool from '@libs/actions/ProfilingTool';
-import {setShouldRecordTroubleshootData} from '@libs/actions/Troubleshoot';
+import {cleanupAfterDisable, disableRecording, enableRecording, stopProfilingAndGetData} from '@libs/actions/Troubleshoot';
+import type {ProfilingData} from '@libs/actions/Troubleshoot';
 import {parseStringifiedMessages} from '@libs/Console';
 import getPlatform from '@libs/getPlatform';
-import Log from '@libs/Log';
-import {Memoize} from '@libs/memoize';
-import Performance from '@libs/Performance';
-import {shouldShowProfileTool as shouldShowProfileToolUtil} from '@userActions/TestTool';
 import CONFIG from '@src/CONFIG';
-import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Log as OnyxLog} from '@src/types/onyx';
 import pkg from '../../../package.json';
-import RNFS from './RNFS';
+import handleStopRecording from './handleStopRecording';
+import type StopRecordingParams from './handleStopRecording.types';
 import Share from './Share';
 
 type File = {
@@ -88,52 +81,26 @@ function BaseRecordTroubleshootDataToolMenu({
     const styles = useThemeStyles();
     const [shouldRecordTroubleshootData] = useOnyx(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, {canBeMissing: true});
     const [capturedLogs] = useOnyx(ONYXKEYS.LOGS, {canBeMissing: true});
-    const [isProfilingInProgress] = useOnyx(ONYXKEYS.APP_PROFILING_IN_PROGRESS, {canBeMissing: true});
     const [shareUrls, setShareUrls] = useState<string[]>();
     const [isDisabled, setIsDisabled] = useState<boolean>(false);
     const [profileTracePath, setProfileTracePath] = useState<string>();
 
-    const shouldShowProfileTool = useMemo(() => shouldShowProfileToolUtil(), []);
-
-    const onToggleProfiling = useCallback(() => {
-        const shouldProfiling = !isProfilingInProgress;
-        if (shouldProfiling) {
-            setShareUrls(undefined);
-            Memoize.startMonitoring();
-            Performance.enableMonitoring();
-            startProfiling();
-        } else {
-            Performance.disableMonitoring();
-        }
-        toggleProfileTool(shouldProfiling);
-        return () => {
-            Performance.disableMonitoring();
-        };
-    }, [isProfilingInProgress]);
-
-    const getAppInfo = useCallback(() => {
-        return Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]).then(([totalMemory, usedMemory]) => {
-            return JSON.stringify({
-                appVersion: pkg.version,
-                environment: CONFIG.ENVIRONMENT,
-                platform: getPlatform(),
-                totalMemory: formatBytes(totalMemory, 2),
-                usedMemory: formatBytes(usedMemory, 2),
-                memoizeStats: Memoize.stopMonitoring(),
-                performance: shouldShowProfileTool ? Performance.getPerformanceMeasures() : undefined,
-            });
+    const getAppInfo = async (profilingData: ProfilingData) => {
+        const [totalMemory, usedMemory] = await Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]);
+        return JSON.stringify({
+            appVersion: pkg.version,
+            environment: CONFIG.ENVIRONMENT,
+            platform: getPlatform(),
+            totalMemory: formatBytes(totalMemory, 2),
+            usedMemory: formatBytes(usedMemory, 2),
+            memoizeStats: profilingData.memoizeStats,
+            performance: profilingData.performanceMeasures,
         });
-    }, [shouldShowProfileTool]);
-
-    const onStopProfiling = useMemo(() => (shouldShowProfileTool ? stopProfiling : () => Promise.resolve()), [shouldShowProfileTool]);
+    };
 
-    const onToggle = () => {
-        if (shouldShowProfileTool) {
-            onToggleProfiling();
-        }
+    const onToggle = async () => {
         if (!shouldRecordTroubleshootData) {
-            setShouldStoreLogs(true);
-            setShouldRecordTroubleshootData(true);
+            enableRecording();
 
             if (onEnableLogging) {
                 onEnableLogging();
@@ -146,8 +113,7 @@ function BaseRecordTroubleshootDataToolMenu({
 
         if (!capturedLogs) {
             Alert.alert(translate('initialSettingsPage.troubleshoot.noLogsToShare'));
-            disableLoggingAndFlushLogs();
-            setShouldRecordTroubleshootData(false);
+            disableRecording();
             return;
         }
 
@@ -156,103 +122,30 @@ function BaseRecordTroubleshootDataToolMenu({
 
         const infoFileName = `App_Info_${pkg.version}.json`;
 
-        if (getPlatform() === CONST.PLATFORM.WEB) {
-            onStopProfiling(true, newFileName).then(() => {
-                getAppInfo().then((appInfo) => {
-                    zipRef.current?.file(infoFileName, appInfo);
-
-                    onDisableLogging(logsWithParsedMessages).then(() => {
-                        disableLoggingAndFlushLogs();
-                        setShouldRecordTroubleshootData(false);
-                        setIsDisabled(false);
-                        onDownloadZip?.();
-                    });
-                });
-            });
-        } else if (getPlatform() === CONST.PLATFORM.IOS) {
-            onStopProfiling(true, newFileName).then((path) => {
-                if (!path) {
-                    return;
-                }
-
-                const newFilePath = `${pathToBeUsed}/${newFileName}`;
-
-                RNFS.exists(newFilePath)
-                    .then((fileExists) => {
-                        if (!fileExists) {
-                            return;
-                        }
-
-                        return RNFS.unlink(newFilePath).then(() => {
-                            Log.hmmm('[ProfilingToolMenu] existing file deleted successfully');
-                        });
-                    })
-                    .catch((error) => {
-                        const typedError = error as Error;
-                        Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', typedError.message);
-                    })
-                    .then(() => {
-                        RNFS.copyFile(path, newFilePath)
-                            .then(() => {
-                                getAppInfo().then((appInfo) => {
-                                    zipRef.current?.file(infoFileName, appInfo);
-
-                                    onDisableLogging(logsWithParsedMessages).then(() => {
-                                        disableLoggingAndFlushLogs();
-                                        setShouldRecordTroubleshootData(false);
-                                        setIsDisabled(false);
-                                        onDownloadZip?.();
-                                    });
-                                });
-                                Log.hmmm('[ProfilingToolMenu] file copied successfully');
-
-                                setProfileTracePath(newFilePath);
-                            })
-                            .catch((err) => {
-                                console.error('[ProfilingToolMenu] error copying file: ', err);
-                            });
-                    })
-                    .catch((error: Record<string, unknown>) => {
-                        console.error('[ProfilingToolMenu] error copying file: ', error);
-                        Log.hmmm('[ProfilingToolMenu] error copying file: ', error);
-                    });
-            });
-        } else if (getPlatform() === CONST.PLATFORM.ANDROID) {
-            onStopProfiling(true, newFileName).then((path) => {
-                if (!path) {
-                    return;
-                }
-
-                RNFetchBlob.fs
-                    // Check if it is an internal path of `DownloadManager` then append content://media to create a valid url
-                    .stat(!path.startsWith('content://media/') && path.match(/\/downloads\/\d+$/) ? `content://media/${path}` : path)
-                    .then(({path: realPath}) => setProfileTracePath(realPath))
-                    .catch(() => setProfileTracePath(path));
-
-                getAppInfo().then((appInfo) => {
-                    zipRef.current?.file(infoFileName, appInfo);
-
-                    onDisableLogging(logsWithParsedMessages).then(() => {
-                        disableLoggingAndFlushLogs();
-                        setShouldRecordTroubleshootData(false);
-                        setIsDisabled(false);
-                    });
-                });
-            });
-        } else {
-            // Desktop
-            onStopProfiling(true, newFileName).then(() => {
-                getAppInfo().then((appInfo) => {
-                    zipRef.current?.file(infoFileName, appInfo);
-
-                    onDisableLogging(logsWithParsedMessages).then(() => {
-                        disableLoggingAndFlushLogs();
-                        setShouldRecordTroubleshootData(false);
-                        setIsDisabled(false);
-                    });
-                });
-            });
+        try {
+            const profilingData = await stopProfilingAndGetData(newFileName);
+            const appInfo = await getAppInfo(profilingData);
+
+            const params: StopRecordingParams = {
+                profilingData,
+                infoFileName,
+                profileFileName: newFileName,
+                appInfo,
+                logsWithParsedMessages,
+                onDisableLogging,
+                cleanupAfterDisable,
+                zipRef,
+                pathToBeUsed,
+                onDownloadZip,
+                setProfileTracePath,
+            };
+
+            await handleStopRecording(params);
+        } catch (error) {
+            console.error('[ProfilingToolMenu] error handling stop recording', error);
         }
+
+        setIsDisabled(false);
     };
 
     useEffect(() => {
@@ -275,7 +168,9 @@ function BaseRecordTroubleshootDataToolMenu({
                 <Switch
                     accessibilityLabel={translate('initialSettingsPage.troubleshoot.recordTroubleshootData')}
                     isOn={!!shouldRecordTroubleshootData}
-                    onToggle={onToggle}
+                    onToggle={() => {
+                        onToggle().catch((error) => console.error('[ProfilingToolMenu] toggle failed', error));
+                    }}
                     disabled={isDisabled}
                 />
             </TestToolRow>
diff --git a/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts b/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts
new file mode 100644
index 000000000000..7fb670ff1bfd
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts
@@ -0,0 +1,19 @@
+import type StopRecordingParams from './handleStopRecording.types';
+
+type FinalizeStopRecordingParams = Pick<StopRecordingParams, 'infoFileName' | 'appInfo' | 'logsWithParsedMessages' | 'onDisableLogging' | 'cleanupAfterDisable' | 'zipRef' | 'onDownloadZip'>;
+
+export default async function finalizeStopRecording({
+    infoFileName,
+    appInfo,
+    logsWithParsedMessages,
+    onDisableLogging,
+    cleanupAfterDisable,
+    zipRef,
+    onDownloadZip,
+}: FinalizeStopRecordingParams): Promise<void> {
+    zipRef.current?.file(infoFileName, appInfo);
+
+    await onDisableLogging(logsWithParsedMessages);
+    cleanupAfterDisable();
+    onDownloadZip?.();
+}
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
new file mode 100644
index 000000000000..194f9810b989
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts
@@ -0,0 +1,37 @@
+import RNFetchBlob from 'react-native-blob-util';
+import finalizeStopRecording from './finalizeStopRecording';
+import type StopRecordingParams from './handleStopRecording.types';
+
+export default async function handleStopRecording({
+    profilingData,
+    infoFileName,
+    appInfo,
+    logsWithParsedMessages,
+    onDisableLogging,
+    cleanupAfterDisable,
+    zipRef,
+    onDownloadZip,
+    setProfileTracePath,
+}: StopRecordingParams): Promise<void> {
+    const {profilePath} = profilingData;
+
+    if (profilePath) {
+        try {
+            // Check if it is an internal path of `DownloadManager` then append content://media to create a valid url
+            const {path} = await RNFetchBlob.fs.stat(!profilePath.startsWith('content://media/') && profilePath.match(/\/downloads\/\d+$/) ? `content://media/${profilePath}` : profilePath);
+            setProfileTracePath?.(path);
+        } catch {
+            setProfileTracePath?.(profilePath);
+        }
+    }
+
+    await finalizeStopRecording({
+        infoFileName,
+        appInfo,
+        logsWithParsedMessages,
+        onDisableLogging,
+        cleanupAfterDisable,
+        zipRef,
+        onDownloadZip,
+    });
+}
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
new file mode 100644
index 000000000000..1f4a28d17953
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts
@@ -0,0 +1,57 @@
+import RNFS from 'react-native-fs';
+import Log from '@libs/Log';
+import finalizeStopRecording from './finalizeStopRecording';
+import type StopRecordingParams from './handleStopRecording.types';
+
+export default async function handleStopRecording({
+    profilingData,
+    infoFileName,
+    profileFileName,
+    appInfo,
+    logsWithParsedMessages,
+    onDisableLogging,
+    cleanupAfterDisable,
+    zipRef,
+    pathToBeUsed,
+    onDownloadZip,
+    setProfileTracePath,
+}: StopRecordingParams): Promise<void> {
+    const {profilePath} = profilingData;
+
+    if (!profilePath) {
+        cleanupAfterDisable();
+        return;
+    }
+
+    const newFilePath = `${pathToBeUsed}/${profileFileName}`;
+
+    try {
+        const fileExists = await RNFS.exists(newFilePath);
+        if (fileExists) {
+            await RNFS.unlink(newFilePath);
+            Log.hmmm('[ProfilingToolMenu] existing file deleted successfully');
+        }
+    } catch (error) {
+        const message = error instanceof Error ? error.message : String(error);
+        Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', message);
+    }
+
+    try {
+        await RNFS.copyFile(profilePath, newFilePath);
+        await finalizeStopRecording({
+            infoFileName,
+            appInfo,
+            logsWithParsedMessages,
+            onDisableLogging,
+            cleanupAfterDisable,
+            zipRef,
+            onDownloadZip,
+        });
+
+        setProfileTracePath?.(newFilePath);
+        Log.hmmm('[ProfilingToolMenu] file copied successfully');
+    } catch (error) {
+        const message = error instanceof Error ? error.message : String(error);
+        Log.hmmm('[ProfilingToolMenu] error copying file: ', message);
+    }
+}
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts
new file mode 100644
index 000000000000..90f32b3b584a
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts
@@ -0,0 +1,17 @@
+import finalizeStopRecording from './finalizeStopRecording';
+import type StopRecordingParams from './handleStopRecording.types';
+
+type HandleStopRecording = (params: StopRecordingParams) => Promise<void>;
+
+const handleStopRecording: HandleStopRecording = ({infoFileName, appInfo, logsWithParsedMessages, onDisableLogging, cleanupAfterDisable, zipRef, onDownloadZip}) =>
+    finalizeStopRecording({
+        infoFileName,
+        appInfo,
+        logsWithParsedMessages,
+        onDisableLogging,
+        cleanupAfterDisable,
+        zipRef,
+        onDownloadZip,
+    });
+
+export default handleStopRecording;
diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts
new file mode 100644
index 000000000000..19eebde4df72
--- /dev/null
+++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts
@@ -0,0 +1,20 @@
+import type JSZip from 'jszip';
+import type {RefObject} from 'react';
+import type {ProfilingData} from '@libs/actions/Troubleshoot';
+import type {Log as OnyxLog} from '@src/types/onyx';
+
+type StopRecordingParams = {
+    profilingData: ProfilingData;
+    infoFileName: string;
+    profileFileName: string;
+    appInfo: string;
+    logsWithParsedMessages: OnyxLog[];
+    onDisableLogging: (logs: OnyxLog[]) => Promise<void>;
+    cleanupAfterDisable: () => void;
+    zipRef: RefObject<InstanceType<typeof JSZip>>;
+    pathToBeUsed: string;
+    onDownloadZip?: () => void;
+    setProfileTracePath?: (path: string) => void;
+};
+
+export default StopRecordingParams;
diff --git a/src/libs/ExportOnyxState/index.native.ts b/src/libs/ExportOnyxState/index.native.ts
index 12cfb612ab1c..8e7e5ad04193 100644
--- a/src/libs/ExportOnyxState/index.native.ts
+++ b/src/libs/ExportOnyxState/index.native.ts
@@ -7,9 +7,18 @@ import type OnyxState from '@src/types/onyx/OnyxState';
 import {maskOnyxState} from './common';
 import type {ExportOnyxStateModule, ReadFromOnyxDatabase, ShareAsFile} from './types';
 
+let onyxDb: ReturnType<typeof open> | null = null;
+
+function getOnyxDb() {
+    if (!onyxDb) {
+        onyxDb = open({name: CONST.DEFAULT_DB_NAME});
+    }
+    return onyxDb;
+}
+
 const readFromOnyxDatabase: ReadFromOnyxDatabase = () =>
     new Promise((resolve) => {
-        const db = open({name: CONST.DEFAULT_DB_NAME});
+        const db = getOnyxDb();
         const query = `SELECT * FROM ${CONST.DEFAULT_TABLE_NAME}`;
 
         db.executeAsync<OnyxSQLiteKeyValuePair>(query, []).then(({rows}) => {
diff --git a/src/libs/actions/Troubleshoot.ts b/src/libs/actions/Troubleshoot.ts
index 8f149d09d132..4b483c69627b 100644
--- a/src/libs/actions/Troubleshoot.ts
+++ b/src/libs/actions/Troubleshoot.ts
@@ -1,13 +1,210 @@
 import Onyx from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
+import {startProfiling, stopProfiling} from 'react-native-release-profiler';
+import {Memoize} from '@libs/memoize';
+import Performance from '@libs/Performance';
 import ONYXKEYS from '@src/ONYXKEYS';
+import {disableLoggingAndFlushLogs, setShouldStoreLogs} from './Console';
+import toggleProfileTool from './ProfilingTool';
+import {shouldShowProfileTool} from './TestTool';
+
+type ProfilingData = {
+    profilePath?: string;
+    memoizeStats: ReturnType<typeof Memoize.stopMonitoring>;
+    performanceMeasures?: ReturnType<typeof Performance.getPerformanceMeasures>;
+};
+
+// Auto-off timeout for troubleshoot recording (10 minutes)
+const AUTO_OFF_TIMEOUT_MS = 10 * 60 * 1000;
+
+// Module-level state
+let autoOffTimeout: NodeJS.Timeout | null = null;
+let shouldRecordTroubleshootData: OnyxEntry<boolean>;
+let troubleshootRecordingStartTime: OnyxEntry<number | null>;
+let isRecordingLoaded = false;
+let isStartTimeLoaded = false;
+let isInitialized = false;
+
+/**
+ * Clear the auto-off timeout if it exists
+ */
+function clearAutoOffTimeout() {
+    if (!autoOffTimeout) {
+        return;
+    }
+
+    clearTimeout(autoOffTimeout);
+    autoOffTimeout = null;
+}
+
+/**
+ * Stop profiling and get profiling data.
+ * Used for manual disable to get profile path and stats before cleanup.
+ * @param fileName - The filename to save the profile trace as
+ * @returns Profile path, memoize stats, and performance measures
+ */
+async function stopProfilingAndGetData(fileName: string): Promise<ProfilingData> {
+    const showProfileTool = shouldShowProfileTool();
+
+    // Stop profiler and save to file (only if profiling is available)
+    const profilePath = showProfileTool ? await stopProfiling(true, fileName) : undefined;
+
+    // Get stats before stopping monitoring
+    const memoizeStats = Memoize.stopMonitoring();
+    const performanceMeasures = showProfileTool ? Performance.getPerformanceMeasures() : undefined;
+
+    // Stop monitoring
+    Performance.disableMonitoring();
+    toggleProfileTool(false);
+
+    return {profilePath, memoizeStats, performanceMeasures};
+}
+
+/**
+ * Disable troubleshoot recording, stop profiling, and clean up logs.
+ * Used for auto-off and invalid state cleanup.
+ */
+function clearRecordingOnyxState() {
+    Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, null);
+    Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false);
+}
+
+function disableRecording() {
+    clearAutoOffTimeout();
+
+    // Stop profiling and performance monitoring
+    Performance.disableMonitoring();
+    Memoize.stopMonitoring();
+    toggleProfileTool(false);
+
+    // Disable logging and flush logs
+    disableLoggingAndFlushLogs();
+
+    // Update Onyx state
+    clearRecordingOnyxState();
+}
+
+/**
+ * Clean up Onyx state and flush logs.
+ * Used after manual disable when profiling was already stopped via stopProfilingAndGetData.
+ */
+function cleanupAfterDisable() {
+    clearAutoOffTimeout();
+    disableLoggingAndFlushLogs();
+    clearRecordingOnyxState();
+}
+
+/**
+ * Schedule auto-off after the given time
+ */
+function scheduleAutoOff(remainingTime: number) {
+    clearAutoOffTimeout();
+    autoOffTimeout = setTimeout(() => {
+        disableRecording();
+        autoOffTimeout = null;
+    }, remainingTime);
+}
+
+/**
+ * Enable troubleshoot recording, start profiling, and enable log storage.
+ */
+function enableRecording() {
+    clearAutoOffTimeout();
+
+    // Enable log storage
+    setShouldStoreLogs(true);
+
+    // Start profiling and performance monitoring if available
+    if (shouldShowProfileTool()) {
+        Memoize.startMonitoring();
+        Performance.enableMonitoring();
+        startProfiling();
+        toggleProfileTool(true);
+    }
+
+    // Update Onyx state and schedule auto-off
+    Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, Date.now());
+    scheduleAutoOff(AUTO_OFF_TIMEOUT_MS);
+    Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, true);
+}
+
+/**
+ * Check recording state on app load and handle accordingly.
+ * - If recording ON but no start time → disable (invalid state)
+ * - If recording ON and start time exists → calculate remaining time or disable if exceeded
+ */
+function tryInitialize() {
+    if (isInitialized || !isRecordingLoaded || !isStartTimeLoaded) {
+        return;
+    }
+    isInitialized = true;
+
+    if (!shouldRecordTroubleshootData) {
+        return;
+    }
+
+    // Recording is ON but no start time - invalid state, disable
+    if (!troubleshootRecordingStartTime) {
+        disableRecording();
+        return;
+    }
+
+    // Calculate remaining time
+    const elapsedTime = Date.now() - troubleshootRecordingStartTime;
+
+    if (elapsedTime >= AUTO_OFF_TIMEOUT_MS) {
+        // Time exceeded, disable recording
+        disableRecording();
+    } else {
+        // Schedule auto-off for remaining time
+        const remainingTime = AUTO_OFF_TIMEOUT_MS - elapsedTime;
+        scheduleAutoOff(remainingTime);
+    }
+}
+
+/**
+ * Listen for changes to the troubleshoot recording flag.
+ * On app load, triggers initialization to handle auto-off timer.
+ */
+Onyx.connectWithoutView({
+    key: ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA,
+    initWithStoredValues: true,
+    callback: (value) => {
+        shouldRecordTroubleshootData = value;
+        isRecordingLoaded = true;
+        tryInitialize();
+    },
+});
+
+/**
+ * Listen for changes to the recording start time.
+ * On app load, triggers initialization to calculate remaining time for auto-off.
+ */
+Onyx.connectWithoutView({
+    key: ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME,
+    initWithStoredValues: true,
+    callback: (value) => {
+        troubleshootRecordingStartTime = value;
+        isStartTimeLoaded = true;
+        tryInitialize();
+    },
+});
 
 /**
  * Set whether or not to record troubleshoot data
  * @param shouldRecord Whether or not to record troubleshoot data
  */
 function setShouldRecordTroubleshootData(shouldRecord: boolean) {
-    Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, shouldRecord);
+    clearAutoOffTimeout();
+
+    if (shouldRecord) {
+        Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, Date.now());
+        scheduleAutoOff(AUTO_OFF_TIMEOUT_MS);
+        Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, true);
+    } else {
+        clearRecordingOnyxState();
+    }
 }
 
-// eslint-disable-next-line import/prefer-default-export
-export {setShouldRecordTroubleshootData};
+export {setShouldRecordTroubleshootData, enableRecording, disableRecording, stopProfilingAndGetData, cleanupAfterDisable};
+export type {ProfilingData};
diff --git a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
index df6ac6966a54..c6ddcea9e2db 100644
--- a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
+++ b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx
@@ -198,7 +198,7 @@ function TroubleshootPage() {
                     >
                         <View style={[styles.flex1, styles.mt5]}>
                             <View>
-                                <RecordTroubleshootDataToolMenu />
+                                {!isProduction && <RecordTroubleshootDataToolMenu />}
                                 <TestToolRow title={translate('initialSettingsPage.troubleshoot.maskExportOnyxStateData')}>
                                     <Switch
                                         accessibilityLabel={translate('initialSettingsPage.troubleshoot.maskExportOnyxStateData')}
