ramensoftware/windhawk · error
file dialog
Error message
file dialog: {message} What it means
dialog_error builds the wire error returned to the Windhawk UI frontend when a native file-dialog (COM/shell) operation fails during import/export of user data. It wraps the OS-level failure message as an ErrorCode::Internal HostError prefixed with 'file dialog:'. The actual cause is whatever COM/shell API reported.
Solutions
- Retry the export/import once — transient shell/COM failures often succeed on a second attempt.
- Try again from a standard local folder (e.g. Desktop or Documents) rather than network drives or shell namespace locations.
- Check the detailed message after the 'file dialog: ' prefix for the underlying COM error and search that specific HRESULT.
- Restart the Windhawk UI process; if persistent, test whether file dialogs work in other apps to rule out a system-wide shell problem.
Example fix
// before
let path = pick_and_read_archive(state).map_err(|e| e)?;
// after
let path = match pick_and_read_archive(state) {
Ok(p) => p,
Err(e) if e.to_string().starts_with("file dialog:") => {
return Ok(json!({ "cancelled": true })); // treat dialog failure as no-op
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: try-catch
Validate before calling
// Raison d'être of dialog_error is OS dialog failure; pre-validate what you can:
// ensure target directory is writable and check size before export
let max_bytes: u64 = 64 * 1024 * 1024;
if archive_path.metadata()?.len() > max_bytes {
return Err(HostError::wire(WireError::new(
ErrorCode::InvalidArgument,
"archive too large".into(),
)));
} Type guard
fn is_dialog_error(e: &HostError) -> bool {
e.message.starts_with("file dialog: ")
} Try / catch
match export_userdata(state, payload) {
Ok(result) => result,
Err(e) if e.message.starts_with("file dialog: ") => {
// surface a friendly message and offer retry, not a crash
eprintln!("Export failed at the file dialog step: {e}");
retry_or_cancel()
}
Err(e) => return Err(e),
} Prevention
- Retry dialog-based operations once before surfacing failure to the user.
- Prefer standard local folders over network/shell namespace locations.
- Log the full message after the 'file dialog: ' prefix to capture the COM HRESULT.
- Keep the shell/COM host thread initialized (CoInitialize) before invoking dialogs.
When it happens
Trigger: finish_export (saving an archive via a save dialog) or pick_and_read_archive (choosing a file via an open dialog) when the COM IFileDialog call fails — dialog creation failure, user cancellation surfaced as an error, or shell/COM initialization problems.
Common situations: Broken or heavily customized shell environments; COM initialization issues in the host process; network/shell namespace locations that fail to enumerate; OS dialog DLL problems after updates.
Understand the failure class
Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.
Related errors
- ATL exception: HRESULT 0x
- error while running the Windhawk UI
- spawn the event pump thread
- spawn the background startup thread
- throw ResultException(failure)
AI-assisted analysis of ramensoftware/windhawk@61d99ed8e1 (2026-09-12).
Data as JSON: /api/errors/9f44b515b749fc5b.
Report an issue: GitHub.
Appendix: source
Thrown at src/windhawk-core/ui/src/commands/userdata.rs:377
e.to_string(),
json!({ "path": path.display().to_string() }),
))
}
/// An over-the-cap archive file as the standard wire error, carrying the failing
/// `path` like [`io_error`]. Coded and worded like the core's own rejection of an
/// oversized archive, so which layer caught it does not change what the
/// front-end shows.
fn too_large_error(path: &Path, size: u64) -> HostError {
HostError::wire(WireError::with_details(
ErrorCode::InvalidRequest,
format!("archive is too large ({size} bytes; the maximum is {MAX_ARCHIVE_BYTES})"),
json!({ "path": path.display().to_string() }),
))
}
/// A file-dialog (COM/shell) failure as an internal wire error.
fn dialog_error(message: &str) -> HostError {
HostError::wire(WireError::new(
ErrorCode::Internal,
format!("file dialog: {message}"),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_archive_name_zero_pads_and_reads_as_a_timestamp() {
assert_eq!(
format_archive_name(2020, 12, 25, 14, 30, 5),
"2020-12-25-14h30m05-windhawk-backup.json"
);
// Single-digit month/day/hour are zero-padded so names sort chronologically.
assert_eq!(View on GitHub (pinned to 61d99ed8e1)