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
- 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).
- 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.
- Check the application log for the StorageError detail logged just before the panic — it names the exact directory whose probe failed.
- 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
- Never place the default ~/.SwitchHosts root on removable/network/read-only media; pick a local writable volume for custom data dirs.
- Free disk and verify home-directory write permissions in your installer/first-run checks before the app boots the storage layer.
- Exercise the degraded paths (custom dir gone, default unusable) in tests so bootstrap's recovery fallbacks actually cover them.
- Replace entry-point .expect() calls with explicit error UI so a storage failure is diagnosable instead of a silent panic.
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.