{"record":{"id":"da52141882a56868","repo":"oldj/SwitchHosts","slug":"failed-to-bootstrap-switchhosts-v5-storage-layer","errorCode":null,"errorMessage":"failed to bootstrap SwitchHosts v5 storage layer","messagePattern":"failed to bootstrap SwitchHosts v5 storage layer","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src-tauri/src/lib.rs","lineNumber":130,"sourceCode":"                std::process::exit(exit_code);\n            }\n        }\n    }\n}\n\n#[cfg_attr(mobile, tauri::mobile_entry_point)]\npub fn run() {\n    // Windows elevation helper: when SwitchHosts is relaunched via\n    // ShellExecuteExW with `runas` to perform a privileged hosts file\n    // write (see `hosts_apply::elevation`), the elevated child re-enters\n    // this function with a special argv shape. Detect it, do the write,\n    // and exit before the v5 storage layer or the Tauri runtime starts.\n    // This block is a no-op on macOS / Linux (they don't self-relaunch).\n    if maybe_run_as_elevation_helper() {\n        return;\n    }\n\n    let state = AppState::bootstrap().expect(\"failed to bootstrap SwitchHosts v5 storage layer\");\n\n    let app = tauri::Builder::default()\n        // Single-instance MUST be the first plugin so a second\n        // launch is intercepted before any other plugin starts up.\n        .plugin(tauri_plugin_single_instance::init(|app, args, cwd| {\n            lifecycle::focus_main_on_second_instance(app, args, cwd)\n        }))\n        // The login-start entry (LaunchAgent / run key / autostart file)\n        // passes a marker flag so a login launch is distinguishable from\n        // the user opening the app — see LOGIN_LAUNCH_ARG in lifecycle.\n        .plugin(tauri_plugin_autostart::init(\n            MacosLauncher::LaunchAgent,\n            Some(vec![lifecycle::LOGIN_LAUNCH_ARG]),\n        ))\n        .plugin(tauri_plugin_dialog::init())\n        .plugin(tauri_plugin_updater::Builder::new().build())\n        .plugin(\n            tauri_plugin_log::Builder::new()","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/oldj/SwitchHosts/blob/6ecea88d9291e0b127cfe745921738a66829a1ba/src-tauri/src/lib.rs#L112-L148","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (src-tauri/src/lib.rs:130)\nlet state = AppState::bootstrap().expect(\"failed to bootstrap SwitchHosts v5 storage layer\");\n\n// after\nlet state = match AppState::bootstrap() {\n    Ok(s) => s,\n    Err(e) => {\n        eprintln!(\"storage bootstrap failed: {e}\");\n        // surface a dialog / pick fallback location instead of panicking\n        show_fatal_storage_dialog(&e);\n        return;\n    }\n};","handlingStrategy":"try-catch","validationCode":"// Before launching the full runtime, verify the default root can host storage\nlet (paths, _) = match storage::paths::resolve_root() {\n    Ok(p) => p,\n    Err(e) => { /* log and surface a setup dialog instead of crashing */ return; }\n};\nif let Err(e) = paths.ensure_usable() {\n    // show the user which directory failed and offer 'Choose New Folder'\n    report_storage_problem(&e);\n    return;\n}","typeGuard":"null","tryCatchPattern":"// Rust: don't .expect() at the entry point — catch the Result (and any panic boundary)\nlet state = match AppState::bootstrap() {\n    Ok(s) => s,\n    Err(err) => {\n        log::error!(\"bootstrap failed: {err}\");\n        show_storage_error_dialog(&err); // tell the user which dir is unusable\n        std::process::exit(1);\n    }\n};","preventionTips":["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."],"tags":["rust","tauri","startup","panic","storage","bootstrap","app-crash"],"backgroundTag":"app-startup-initialization-failure","analyzedSha":"6ecea88d9291e0b127cfe745921738a66829a1ba","analyzedAt":"2026-08-16T21:20:57.238Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}