{"record":{"id":"d900e3cc2b583ee2","repo":"gitbutlerapp/gitbutler","slug":"failed-to-create-app-settings-d900e3","errorCode":null,"errorMessage":"failed to create app settings","messagePattern":"failed to create app settings","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/gitbutler-tauri/src/main.rs","lineNumber":72,"sourceCode":"    std::fs::create_dir_all(&config_dir).expect(\"failed to create config dir\");\n    let custom_settings = cfg!(feature = \"packaged-but-distribution\")\n        .then(but_settings::customization::packaged_but_binary);\n    // While it serves a function, this behavior is sub-optimal. The proper solution is to decouple:\n    // - Checking for updates from\n    // - Performing an update\n    // This way people can be informed that there is an update even if self-updating is not possible (i.e. installed via package manager).\n    let custom_settings = if cfg!(feature = \"disable-auto-updates\") {\n        but_settings::customization::merge_two(\n            but_settings::customization::disable_auto_update_checks(),\n            custom_settings,\n        )\n        .into()\n    } else {\n        custom_settings\n    };\n    let mut app_settings =\n        AppSettingsWithDiskSync::new_with_customization(config_dir.clone(), custom_settings)\n            .expect(\"failed to create app settings\");\n\n    if let Ok(updated_csp) = csp_with_extras(\n        tauri_context.config().app.security.csp.as_ref().cloned(),\n        &app_settings,\n    ) {\n        tauri_context.config_mut().app.security.csp = updated_csp;\n    };\n\n    if let Some(project_to_open) =\n        std::env::var_os(\"GITBUTLER_PROJECT_DIR\").map(std::path::PathBuf::from)\n    {\n        bail!(\n            \"GUI says: how do we tell the frontend to open: {}? \\\n               We could figure out the project-ID while that's important, and pass it along somehow\",\n            project_to_open.display()\n        );\n    }\n    let (app_data_dir, app_cache_dir, app_log_dir) = (","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-tauri/src/main.rs#L54-L90","documentation":"AppSettingsWithDiskSync::new_with_customization (but-settings/src/watch.rs:77) loads settings.json from the config dir - creating it with '{}' when absent - then reads, leniently parses, merges over defaults, and finally deserializes into typed AppSettings (persistence.rs:57-95). Its Result is unwrapped with expect(\"failed to create app settings\"), so startup panics when the file cannot be created/read (IO, permissions), is unparseable even for the lenient parser (truncated file from a crash mid-write), or contains a value whose type contradicts the settings schema.","triggerScenarios":"App startup with a corrupted ~/.config/gitbutler/settings.json (partial write after power loss or crash), a settings file from another version with incompatible field types, or IO errors creating/reading the file in the config directory.","commonSituations":"Forced shutdowns while settings were being saved, hand-edited settings files, sync clients (Dropbox/OneDrive) leaving placeholder or partially-synced files, and downgrades after an upgrade changed a field's type.","solutions":["Rename or delete the settings file so it is regenerated with defaults: mv ~/.config/gitbutler/settings.json ~/.config/gitbutler/settings.json.bak","Read the error context - it names the exact path and whether reading or parsing failed - and fix the JSON/type problem in place","Verify the file is readable/writable by the current user and not locked by sync tools","As a contributor: use ? instead of expect so main() prints the full error chain (see exampleFix)"],"exampleFix":"// before\nlet mut app_settings = AppSettingsWithDiskSync::new_with_customization(config_dir.clone(), custom_settings)\n    .expect(\"failed to create app settings\");\n\n// after (main() already returns Result, as neighboring `?` calls show)\nlet mut app_settings = AppSettingsWithDiskSync::new_with_customization(config_dir.clone(), custom_settings)?;","handlingStrategy":"fallback","validationCode":"// Validate the settings file before app startup\nfn settings_file_ok(path: &std::path::Path) -> bool {\n    match std::fs::read_to_string(path) {\n        Ok(text) => serde_json::from_str::<serde_json::Value>(&text).is_ok(),\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => true,\n        Err(_) => false,\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Back up and regenerate settings.json when startup fails after a crash or downgrade (mv settings.json settings.json.bak)","Keep settings writes atomic (write-temp-then-rename) if you extend the settings code","Exclude the config directory from sync tools' partial-file windows, or pause sync during app runtime"],"tags":["tauri","settings","json","startup","filesystem","panic"],"backgroundTag":"corrupted-settings-file","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}