Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 76 additions & 3 deletions apps/desktop/src-tauri/src/local_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1431,14 +1431,61 @@ fn start_local_proxy_blocking<R: Runtime>(
}

emit_start_progress(&app, "startingProxy", 18, None, None);
let started = start_server(app.clone())?;
// Every failure after the client was stopped must restore the client, or
// the user is left without a working ChatGPT/Codex and no way to recover.
let started = match start_server(app.clone()) {
Ok(value) => value,
Err(error) => {
let client_note = recover_client_after_failed_start(
&app,
client_was_running,
launch_target.as_ref(),
);
emit_start_progress(&app, "failed", 18, None, None);
return Err(format!(
"Local proxy could not be started on port {LOCAL_PROXY_PORT} ({error}).{client_note}"
));
}
};
if let Err(error) = providers::apply_local_proxy_config_for_state(&app) {
if started {
stop_server();
}
return Err(error);
let client_note =
recover_client_after_failed_start(&app, client_was_running, launch_target.as_ref());
emit_start_progress(&app, "failed", 18, None, None);
return Err(format!(
"Local proxy configuration could not be applied ({error}).{client_note}"
));
}
if let Err(error) = set_local_proxy_enabled(&paths, true) {
if started {
stop_server();
}
// The Codex config already points at the local proxy. Roll it back to the
// official direct configuration before restoring the client, otherwise the
// "restored" client would still target the now-stopped proxy and remain
// unusable.
let mut rollback_errors = Vec::new();
if let Err(rollback_error) = providers::restore_official_config(&paths) {
rollback_errors.push(format!("Codex config rollback failed: {rollback_error}"));
}
let client_note =
recover_client_after_failed_start(&app, client_was_running, launch_target.as_ref());
if !client_note.is_empty() {
rollback_errors.push(client_note);
}
emit_start_progress(&app, "failed", 18, None, None);
if rollback_errors.is_empty() {
return Err(format!(
"Local proxy state could not be saved ({error}). The original Codex configuration and ChatGPT/Codex were restored."
));
}
return Err(format!(
"Local proxy state could not be saved ({error}). {}",
rollback_errors.join(" ")
));
}
set_local_proxy_enabled(&paths, true)?;
app.emit("providers-changed", ())
.map_err(|error| error.to_string())?;
crate::system_tray::refresh_menu(&app);
Expand Down Expand Up @@ -1502,6 +1549,32 @@ fn start_local_proxy_blocking<R: Runtime>(
}
}

/// Restart the ChatGPT/Codex client that proxy startup stopped, so a startup
/// failure never leaves the user without a working client. Returns a message
/// describing the restart failure, or an empty string when the client is fine.
fn recover_client_after_failed_start<R: Runtime>(
app: &tauri::AppHandle<R>,
client_was_running: bool,
launch_target: Option<&crate::commands::ChatGptLaunchTarget>,
) -> String {
if !client_was_running {
return String::new();
}
let restart_result = crate::dream_skin::restart_active_session().and_then(|restarted| {
if restarted {
Ok(())
} else {
crate::commands::start_chatgpt(launch_target)
}
});
match restart_result {
Ok(()) => String::new(),
Err(error) => format!(
" ChatGPT/Codex could not be restarted ({error}). Please start ChatGPT or Codex manually."
),
}
}

#[tauri::command]
pub(crate) async fn stop_local_proxy<R: Runtime + 'static>(
app: tauri::AppHandle<R>,
Expand Down