Skip to content

Commit c8ba35e

Browse files
gschierclaude
andauthored
Gracefully handle plugin init failures (#424)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8a330ad commit c8ba35e

5 files changed

Lines changed: 59 additions & 11 deletions

File tree

crates-tauri/yaak-app/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,13 +1383,12 @@ async fn cmd_reload_plugins<R: Runtime>(
13831383
app_handle: AppHandle<R>,
13841384
window: WebviewWindow<R>,
13851385
plugin_manager: State<'_, PluginManager>,
1386-
) -> YaakResult<()> {
1386+
) -> YaakResult<Vec<(String, String)>> {
13871387
let plugins = app_handle.db().list_plugins()?;
13881388
let plugin_context =
13891389
PluginContext::new(Some(window.label().to_string()), window.workspace_id());
1390-
let _errors = plugin_manager.initialize_all_plugins(plugins, &plugin_context).await;
1391-
// Note: errors are returned but we don't show toasts here since this is a manual reload
1392-
Ok(())
1390+
let errors = plugin_manager.initialize_all_plugins(plugins, &plugin_context).await;
1391+
Ok(errors)
13931392
}
13941393

13951394
#[tauri::command]
@@ -1731,6 +1730,7 @@ pub fn run() {
17311730
git_ext::cmd_git_rm_remote,
17321731
//
17331732
// Plugin commands
1733+
plugins_ext::cmd_plugin_init_errors,
17341734
plugins_ext::cmd_plugins_install_from_directory,
17351735
plugins_ext::cmd_plugins_search,
17361736
plugins_ext::cmd_plugins_install,

crates-tauri/yaak-app/src/plugins_ext.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@ pub async fn cmd_plugins_uninstall<R: Runtime>(
198198
Ok(delete_and_uninstall(plugin_manager, &query_manager, &plugin_context, plugin_id).await?)
199199
}
200200

201+
#[command]
202+
pub async fn cmd_plugin_init_errors(
203+
plugin_manager: State<'_, PluginManager>,
204+
) -> Result<Vec<(String, String)>> {
205+
Ok(plugin_manager.take_init_errors().await)
206+
}
207+
201208
#[command]
202209
pub async fn cmd_plugins_updates<R: Runtime>(
203210
app_handle: AppHandle<R>,
@@ -306,7 +313,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
306313
dev_mode,
307314
)
308315
.await
309-
.expect("Failed to initialize plugins");
316+
.expect("Failed to start plugin runtime");
310317

311318
app_handle_clone.manage(manager);
312319
});

crates/yaak-plugins/src/manager.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ pub struct PluginManager {
5050
vendored_plugin_dir: PathBuf,
5151
pub(crate) installed_plugin_dir: PathBuf,
5252
dev_mode: bool,
53+
/// Errors from plugin initialization, retrievable once via `take_init_errors`.
54+
init_errors: Arc<Mutex<Vec<(String, String)>>>,
5355
}
5456

5557
/// Callback for plugin initialization events (e.g., toast notifications)
@@ -93,6 +95,7 @@ impl PluginManager {
9395
vendored_plugin_dir,
9496
installed_plugin_dir,
9597
dev_mode,
98+
init_errors: Default::default(),
9699
};
97100

98101
// Forward events to subscribers
@@ -183,17 +186,21 @@ impl PluginManager {
183186

184187
let init_errors = plugin_manager.initialize_all_plugins(plugins, plugin_context).await;
185188
if !init_errors.is_empty() {
186-
let joined = init_errors
187-
.into_iter()
188-
.map(|(dir, err)| format!("{dir}: {err}"))
189-
.collect::<Vec<_>>()
190-
.join("; ");
191-
return Err(PluginErr(format!("Failed to initialize plugin(s): {joined}")));
189+
for (dir, err) in &init_errors {
190+
warn!("Plugin failed to initialize: {dir}: {err}");
191+
}
192+
*plugin_manager.init_errors.lock().await = init_errors;
192193
}
193194

194195
Ok(plugin_manager)
195196
}
196197

198+
/// Take any initialization errors, clearing them from the manager.
199+
/// Returns a list of `(plugin_directory, error_message)` pairs.
200+
pub async fn take_init_errors(&self) -> Vec<(String, String)> {
201+
std::mem::take(&mut *self.init_errors.lock().await)
202+
}
203+
197204
/// Get the vendored plugin directory path (resolves dev mode path if applicable)
198205
pub fn get_plugins_dir(&self) -> PathBuf {
199206
if self.dev_mode {

src-web/lib/initGlobalListeners.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,39 @@ export function initGlobalListeners() {
123123
console.log('Got plugin updates event', payload);
124124
showPluginUpdatesToast(payload);
125125
});
126+
127+
// Check for plugin initialization errors
128+
invokeCmd<[string, string][]>('cmd_plugin_init_errors').then((errors) => {
129+
for (const [dir, message] of errors) {
130+
const dirBasename = dir.split('/').pop() ?? dir;
131+
showToast({
132+
id: `plugin-init-error-${dirBasename}`,
133+
color: 'warning',
134+
timeout: null,
135+
message: (
136+
<VStack>
137+
<h2 className="font-semibold">Plugin failed to load</h2>
138+
<p className="text-text-subtle text-sm">
139+
{dirBasename}: {message}
140+
</p>
141+
</VStack>
142+
),
143+
action: ({ hide }) => (
144+
<Button
145+
size="xs"
146+
color="warning"
147+
variant="border"
148+
onClick={() => {
149+
hide();
150+
openSettings.mutate('plugins:installed');
151+
}}
152+
>
153+
View Plugins
154+
</Button>
155+
),
156+
});
157+
}
158+
});
126159
}
127160

128161
function showUpdateInstalledToast(version: string) {

src-web/lib/tauri.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type TauriCmd =
4242
| 'cmd_new_child_window'
4343
| 'cmd_new_main_window'
4444
| 'cmd_plugin_info'
45+
| 'cmd_plugin_init_errors'
4546
| 'cmd_reload_plugins'
4647
| 'cmd_render_template'
4748
| 'cmd_save_response'

0 commit comments

Comments
 (0)