oldj/SwitchHosts · critical

failed to bootstrap SwitchHosts v5 storage layer

Error message

failed to bootstrap SwitchHosts v5 storage layer

What it means

src-tauri/src/lib.rs:130 calls AppState::bootstrap().expect("failed to bootstrap SwitchHosts v5 storage layer"), so any Err returned by bootstrap panics and kills the process before the Tauri runtime starts. Per bootstrap (src-tauri/src/storage/mod.rs:82), the fatal path on a normal startup is narrow: resolve_root/default_root IO failures, or — as the code comment states — the case where the resolved root IS the default, ensure_usable fails (see error 0), and there is no active recovery flow to fall back on. Custom-directory failures instead degrade into a recovery dialog rather than this panic.

Source

Thrown at src-tauri/src/lib.rs:130

                std::process::exit(exit_code);
            }
        }
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    // Windows elevation helper: when SwitchHosts is relaunched via
    // ShellExecuteExW with `runas` to perform a privileged hosts file
    // write (see `hosts_apply::elevation`), the elevated child re-enters
    // this function with a special argv shape. Detect it, do the write,
    // and exit before the v5 storage layer or the Tauri runtime starts.
    // This block is a no-op on macOS / Linux (they don't self-relaunch).
    if maybe_run_as_elevation_helper() {
        return;
    }

    let state = AppState::bootstrap().expect("failed to bootstrap SwitchHosts v5 storage layer");

    let app = tauri::Builder::default()
        // Single-instance MUST be the first plugin so a second
        // launch is intercepted before any other plugin starts up.
        .plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
            lifecycle::focus_main_on_second_instance(app, args, cwd)
        }))
        // The login-start entry (LaunchAgent / run key / autostart file)
        // passes a marker flag so a login launch is distinguishable from
        // the user opening the app — see LOGIN_LAUNCH_ARG in lifecycle.
        .plugin(tauri_plugin_autostart::init(
            MacosLauncher::LaunchAgent,
            Some(vec![lifecycle::LOGIN_LAUNCH_ARG]),
        ))
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_updater::Builder::new().build())
        .plugin(
            tauri_plugin_log::Builder::new()

View on GitHub (pinned to 6ecea88d92)

Solutions

  1. Fix the underlying writability problem on the default data root (~/.SwitchHosts): free disk space, chmod u+w / fix ACLs, or unmount the read-only volume it lives on (this resolves the most common cause — see error 0).
  2. If ~/.SwitchHosts is irreparably stuck, rename it (mv ~/.SwitchHosts ~/.SwitchHosts.bak) and relaunch so bootstrap can recreate the v5 layout; migrate needed data back afterwards.
  3. Check the application log for the StorageError detail logged just before the panic — it names the exact directory whose probe failed.
  4. Longer term (maintainer fix): replace the .expect with a graceful error UI or an automatic switch to an alternate writable location, since bootstrap already has recovery machinery for custom dirs.

Example fix

// before (src-tauri/src/lib.rs:130)
let state = AppState::bootstrap().expect("failed to bootstrap SwitchHosts v5 storage layer");

// after
let state = match AppState::bootstrap() {
    Ok(s) => s,
    Err(e) => {
        eprintln!("storage bootstrap failed: {e}");
        // surface a dialog / pick fallback location instead of panicking
        show_fatal_storage_dialog(&e);
        return;
    }
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Before launching the full runtime, verify the default root can host storage
let (paths, _) = match storage::paths::resolve_root() {
    Ok(p) => p,
    Err(e) => { /* log and surface a setup dialog instead of crashing */ return; }
};
if let Err(e) = paths.ensure_usable() {
    // show the user which directory failed and offer 'Choose New Folder'
    report_storage_problem(&e);
    return;
}

Type guard

null

Try / catch

// Rust: don't .expect() at the entry point — catch the Result (and any panic boundary)
let state = match AppState::bootstrap() {
    Ok(s) => s,
    Err(err) => {
        log::error!("bootstrap failed: {err}");
        show_storage_error_dialog(&err); // tell the user which dir is unusable
        std::process::exit(1);
    }
};

Prevention

When it happens

Trigger: Launching the app when the default (~/.SwitchHosts) data root fails ensure_usable (unwritable per error 0: read-only home, bad perms, disk full) on a NORMAL startup — data_dir_recovery.is_none() so bootstrap returns Err at storage/mod.rs:102, and the expect at lib.rs:130 turns it into an abort. Also triggered by unexpected IO errors from resolve_root (pointer file unreadable beyond fallback) or default_root creation failing entirely.

Common situations: Home directory on a read-only or full volume; permissions on ~/.SwitchHosts changed by a restore/migration tool or another user account; enterprise sandbox/MDM blocking writes to the default location; leftover corrupt dir state after an interrupted update. Distinct from error 0 in that this is the user-visible crash at launch, not the underlying storage condition.

Related errors


AI-assisted analysis of oldj/SwitchHosts@6ecea88d92 (2026-08-16). Data as JSON: /api/errors/da52141882a56868. Report an issue: GitHub.