aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-21 00:29:45 +0300
committerGitHub <noreply@github.com>2026-09-21 00:29:45 +0300
commit0ff0f27e222633c97ba4291f6b9be4137002ca84 (patch)
tree97ee77c8a85801ab90133492ddd71b3ce635b8fb /src/app
parentf090ce95d05d2e7811869be93f9a23bf4ac2c3af (diff)
fix: preserve session layouts across shutdown and restore failures (#4400)
* fix: preserve session layouts across shutdown and restore failures refs #4320 * test: update subscription fixture for pane restore errors --------- Co-authored-by: JJ Liebig <jonathan.liebig@gmail.com>
Diffstat (limited to 'src/app')
-rw-r--r--src/app/agent_resume.rs58
-rw-r--r--src/app/creation.rs1
-rw-r--r--src/app/session.rs6
3 files changed, 62 insertions, 3 deletions
diff --git a/src/app/agent_resume.rs b/src/app/agent_resume.rs
index 2cbd93ab..9bf2157f 100644
--- a/src/app/agent_resume.rs
+++ b/src/app/agent_resume.rs
@@ -233,6 +233,15 @@ impl App {
return false;
};
+ if !cwd.is_dir() {
+ if let Some(terminal) = self.state.terminals.get_mut(&terminal_id) {
+ terminal.pending_agent_resume_plan = None;
+ terminal.restore_error = Some("Saved directory is unavailable. Restore the directory and restart this session.".into());
+ terminal.revision = terminal.revision.saturating_add(1);
+ }
+ return true;
+ }
+
let runtime = match crate::terminal::TerminalRuntime::spawn(
pane_id,
rows,
@@ -257,9 +266,11 @@ impl App {
"failed to start shell for deferred agent resume"
);
if let Some(terminal) = self.state.terminals.get_mut(&terminal_id) {
- terminal.clear_agent_runtime_identity_after_respawn();
+ terminal.pending_agent_resume_plan = None;
+ terminal.restore_error = Some(format!("Could not start the saved shell: {err}. Fix the shell configuration and restart this session."));
+ terminal.revision = terminal.revision.saturating_add(1);
}
- return false;
+ return true;
}
};
@@ -380,6 +391,49 @@ mod tests {
#[cfg(unix)]
#[tokio::test]
+ async fn failed_deferred_restore_keeps_session_reference_without_retrying_elsewhere() {
+ for missing_shell in [false, true] {
+ let mut app = test_app();
+ let workspace = crate::workspace::Workspace::test_new("unavailable");
+ let pane_id = workspace.tabs[0].root_pane;
+ let terminal_id = workspace.terminal_id(pane_id).unwrap().clone();
+ app.state.workspaces = vec![workspace];
+ app.state.active = Some(0);
+ app.state.ensure_test_terminals();
+ if missing_shell {
+ app.state.default_shell = "__herdr_missing_resume_shell__".into();
+ }
+ let terminal = app.state.terminals.get_mut(&terminal_id).unwrap();
+ if !missing_shell {
+ terminal.cwd = std::env::current_dir()
+ .unwrap()
+ .join("__herdr_missing_resume_cwd__");
+ assert!(!terminal.cwd.exists());
+ }
+ let session = crate::agent_resume::PersistedAgentSession {
+ source: "herdr:codex".into(),
+ agent: "codex".into(),
+ session_ref: crate::agent_resume::AgentSessionRef::id("resume-test").unwrap(),
+ };
+ terminal.persisted_agent_session = Some(session.clone());
+ terminal.pending_agent_resume_plan = Some(crate::agent_resume::AgentResumePlan {
+ agent: "codex".into(),
+ argv: long_running_test_argv(),
+ dedupe_key: "resume-test".into(),
+ });
+ app.start_pending_agent_resume_for_terminal(&terminal_id, 24, 80, true);
+ assert!(app.terminal_runtimes.get(&terminal_id).is_none());
+ let terminal = &app.state.terminals[&terminal_id];
+ assert!(terminal.pending_agent_resume_plan.is_none());
+ assert_eq!(terminal.persisted_agent_session.as_ref(), Some(&session));
+ assert!(terminal.restore_error.is_some());
+ assert!(!app.has_pending_agent_resumes());
+ assert!(!app.start_pending_agent_resume_for_terminal(&terminal_id, 24, 80, true));
+ }
+ }
+
+ #[cfg(unix)]
+ #[tokio::test]
async fn pending_agent_resume_waits_for_host_theme_before_launch() {
let mut app = test_app();
let workspace = crate::workspace::Workspace::test_new("restored");
diff --git a/src/app/creation.rs b/src/app/creation.rs
index af34e540..05736fe5 100644
--- a/src/app/creation.rs
+++ b/src/app/creation.rs
@@ -340,6 +340,7 @@ impl App {
foreground_cwd: ws.tabs[tab_idx]
.foreground_cwd_for_pane(pane_id, &self.terminal_runtimes)
.map(|cwd| cwd.display().to_string()),
+ restore_error: terminal.restore_error.clone(),
label: terminal.manual_label.clone(),
agent: terminal.effective_agent_label().map(str::to_string),
title: presentation.title,
diff --git a/src/app/session.rs b/src/app/session.rs
index 08c4cd44..509d4050 100644
--- a/src/app/session.rs
+++ b/src/app/session.rs
@@ -49,7 +49,11 @@ impl App {
self.state.selected,
);
let history = self.persist_pane_history.then(|| {
- crate::persist::capture_history(&self.state.workspaces, &self.terminal_runtimes)
+ crate::persist::capture_history(
+ &snapshot,
+ &self.state.workspaces,
+ &self.terminal_runtimes,
+ )
});
SessionSaveJob::Save { snapshot, history }
}