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

  1. Retry the export/import once — transient shell/COM failures often succeed on a second attempt.
  2. Try again from a standard local folder (e.g. Desktop or Documents) rather than network drives or shell namespace locations.
  3. Check the detailed message after the 'file dialog: ' prefix for the underlying COM error and search that specific HRESULT.
  4. 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

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


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)