mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
Improve reset local data guidance for locked Windows files (#1811)
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
Steven Enamakel
parent
7659b40829
commit
159c0aa8fe
@@ -50,8 +50,9 @@ const SettingsHome = () => {
|
||||
setError(null);
|
||||
const currentUserId = snapshot.auth.userId ?? snapshot.currentUser?._id ?? null;
|
||||
await clearAllAppData({ clearSession, userId: currentUserId }); // restarts the app
|
||||
} catch (_error) {
|
||||
setError(t('clearData.failed'));
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
setError(message || t('clearData.failed'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@@ -179,5 +179,35 @@ describe('SettingsHome', () => {
|
||||
expect(args).toMatchObject({ userId: null });
|
||||
expect(typeof args.clearSession).toBe('function');
|
||||
});
|
||||
|
||||
it('surfaces the core error message when clearAllAppData fails (Windows file-lock guidance)', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockClearAllAppData.mockRejectedValueOnce(
|
||||
new Error(
|
||||
'Failed to remove C:\\Users\\me\\.openhuman because it is locked by another OpenHuman window or process. Close all OpenHuman windows and try again.'
|
||||
)
|
||||
);
|
||||
renderSettingsHome();
|
||||
|
||||
await user.click(screen.getByText('Clear App Data').closest('button')!);
|
||||
const confirmButtons = screen.getAllByRole('button', { name: /Clear App Data/i });
|
||||
await user.click(confirmButtons[confirmButtons.length - 1]);
|
||||
|
||||
expect(
|
||||
await screen.findByText(/locked by another OpenHuman window or process/)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('falls back to the translated message when the error has no message', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockClearAllAppData.mockRejectedValueOnce(new Error(''));
|
||||
renderSettingsHome();
|
||||
|
||||
await user.click(screen.getByText('Clear App Data').closest('button')!);
|
||||
const confirmButtons = screen.getAllByRole('button', { name: /Clear App Data/i });
|
||||
await user.click(confirmButtons[confirmButtons.length - 1]);
|
||||
|
||||
expect(await screen.findByText(/Failed to clear data and logout/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +34,9 @@ const Welcome = () => {
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
log('clearAllAppData failed: %s', message);
|
||||
setResetError('Could not clear app data. Please quit and reopen OpenHuman, then try again.');
|
||||
setResetError(
|
||||
message || 'Could not clear app data. Please quit and reopen OpenHuman, then try again.'
|
||||
);
|
||||
setIsClearingAppData(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -191,11 +191,22 @@ describe('Welcome — decryption-failure recovery action', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: /Clear app data & restart/ }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Could not clear app data/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/reset blew up/)).toBeInTheDocument();
|
||||
});
|
||||
// Button re-enabled so the user can retry.
|
||||
expect(screen.getByRole('button', { name: /Clear app data & restart/ })).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('falls back to the generic message when the error has no message', async () => {
|
||||
mockClearAllAppData.mockRejectedValueOnce(new Error(''));
|
||||
|
||||
renderWithProviders(<Welcome />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /Clear app data & restart/ }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Could not clear app data/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Welcome — Select runtime button', () => {
|
||||
|
||||
@@ -92,6 +92,42 @@ fn config_openhuman_dir(config: &Config) -> PathBuf {
|
||||
.map_or_else(|| PathBuf::from("."), PathBuf::from)
|
||||
}
|
||||
|
||||
fn is_windows_file_lock_error(error: &std::io::Error) -> bool {
|
||||
cfg!(windows) && matches!(error.raw_os_error(), Some(32 | 33))
|
||||
}
|
||||
|
||||
fn reset_local_data_remove_error(path: &Path, error: &std::io::Error) -> String {
|
||||
if is_windows_file_lock_error(error) {
|
||||
tracing::warn!(
|
||||
path = %path.display(),
|
||||
error = %error,
|
||||
"[config] reset_local_data: Windows file lock blocked local data deletion"
|
||||
);
|
||||
return format!(
|
||||
"Failed to remove {} because it is locked by another OpenHuman window or process. Close all OpenHuman windows and try again. ({error})",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
format!("Failed to remove {}: {error}", path.display())
|
||||
}
|
||||
|
||||
fn reset_local_data_marker_remove_error(path: &Path, error: &std::io::Error) -> String {
|
||||
if is_windows_file_lock_error(error) {
|
||||
tracing::warn!(
|
||||
marker = %path.display(),
|
||||
error = %error,
|
||||
"[config] reset_local_data: Windows file lock blocked active workspace marker deletion"
|
||||
);
|
||||
return format!(
|
||||
"Failed to remove active workspace marker {} because it is locked by another OpenHuman window or process. Close all OpenHuman windows and try again. ({error})",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
format!("Failed to remove active workspace marker: {error}")
|
||||
}
|
||||
|
||||
/// Internal helper to reset local data by removing specific directories and markers.
|
||||
async fn reset_local_data_for_paths(
|
||||
current_openhuman_dir: &Path,
|
||||
@@ -108,9 +144,12 @@ async fn reset_local_data_for_paths(
|
||||
let mut removed_paths = Vec::new();
|
||||
|
||||
if active_workspace_marker.exists() {
|
||||
tokio::fs::remove_file(&active_workspace_marker)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to remove active workspace marker: {e}"))?;
|
||||
if let Err(error) = tokio::fs::remove_file(&active_workspace_marker).await {
|
||||
return Err(reset_local_data_marker_remove_error(
|
||||
&active_workspace_marker,
|
||||
&error,
|
||||
));
|
||||
}
|
||||
tracing::debug!(
|
||||
marker = %active_workspace_marker.display(),
|
||||
"[config] reset_local_data: removed active workspace marker"
|
||||
@@ -127,9 +166,9 @@ async fn reset_local_data_for_paths(
|
||||
continue;
|
||||
}
|
||||
|
||||
tokio::fs::remove_dir_all(target_dir)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to remove {}: {e}", target_dir.display()))?;
|
||||
if let Err(error) = tokio::fs::remove_dir_all(target_dir).await {
|
||||
return Err(reset_local_data_remove_error(target_dir, &error));
|
||||
}
|
||||
tracing::debug!(
|
||||
dir = %target_dir.display(),
|
||||
"[config] reset_local_data: removed directory"
|
||||
|
||||
@@ -112,6 +112,28 @@ fn config_openhuman_dir_returns_config_path_parent() {
|
||||
assert_eq!(config_openhuman_dir(&cfg), PathBuf::from("/tmp/xyz"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn reset_local_data_remove_error_explains_windows_file_locks() {
|
||||
let err = std::io::Error::from_raw_os_error(32);
|
||||
let msg =
|
||||
reset_local_data_remove_error(std::path::Path::new("C:\\Users\\me\\.openhuman"), &err);
|
||||
|
||||
assert!(msg.contains("locked by another OpenHuman window or process"));
|
||||
assert!(msg.contains("Close all OpenHuman windows and try again"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn reset_local_data_remove_error_explains_windows_lock_violation() {
|
||||
let err = std::io::Error::from_raw_os_error(33);
|
||||
let msg =
|
||||
reset_local_data_remove_error(std::path::Path::new("C:\\Users\\me\\.openhuman"), &err);
|
||||
|
||||
assert!(msg.contains("locked by another OpenHuman window or process"));
|
||||
assert!(msg.contains("Close all OpenHuman windows and try again"));
|
||||
}
|
||||
|
||||
// ── get_runtime_flags / set_browser_allow_all ─────────────────
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user