Report terminal program status with OSC 7501 (#52725)

## Why

Lifecycle status reporting was limited to direct iTerm2 sessions. OSC 7501 lets other terminals receive Codex's program state.

## What changed

- Emit OSC 7501 with `app=codex` and states `idle`, `working`, or `blocked` whenever stdout is a terminal, including multiplexed sessions. Report `blocked` when user action is required.
- Continue emitting iTerm2's OSC 21337 status and bounded activity details in direct iTerm2 sessions.
- Clear status before handing the terminal to an external program, as well as on suspension and exit, and re-emit the current state after returning.

## Testing

Add coverage for OSC 7501 encoding, combined iTerm2 output, clearing both protocols, and protocol selection across terminal and multiplexer environments. Update lifecycle tests and status snapshots for the new protocol.

GitOrigin-RevId: 6d2a6885687b0e7af825e954fab72db0765c9a07
This commit is contained in:
Felipe Coury
2026-10-09 23:43:10 +00:00
committed by copyberry
parent 7efd184c24
commit 4bad6d78e9
11 changed files with 257 additions and 138 deletions
+1 -1
View File
@@ -353,7 +353,7 @@ impl App {
&event,
TuiEvent::Draw | TuiEvent::Resume | TuiEvent::Resize(_) | TuiEvent::FocusGained
) {
self.chat_widget.refresh_iterm_session_status();
self.chat_widget.refresh_terminal_program_status();
}
let width = tui.terminal.last_known_screen_size.width.max(/*other*/ 1);
let footer = self.prompt_navigation_footer(width.saturating_sub(/*rhs*/ 2));
+5 -5
View File
@@ -1182,16 +1182,16 @@ impl ChatWidget {
}
self.refresh_status_line_if_workspace_headline_due();
self.refresh_thread_usage_if_settlement_due();
self.refresh_iterm_session_status();
self.refresh_terminal_program_status();
}
pub(crate) fn refresh_iterm_session_status(&self) {
let status = self.desired_iterm_session_status();
if let Err(err) = crate::iterm_session_status::set_iterm_session_status(
pub(crate) fn refresh_terminal_program_status(&self) {
let status = self.desired_program_status();
if let Err(err) = crate::terminal_program_status::set_terminal_program_status(
status,
self.iterm_session_detail(status),
) {
tracing::debug!(error = %err, "failed to set iTerm2 session status");
tracing::debug!(error = %err, "failed to set terminal program status");
}
}
@@ -8,12 +8,12 @@ use crate::bottom_pane::status_line_from_segments;
use crate::branch_summary;
use crate::chatwidget::limit_label_for_window;
use crate::chatwidget::rate_limits::get_limits_duration;
use crate::iterm_session_status::ItermSessionStatus;
use crate::legacy_core::config::Config;
use crate::model_catalog::LUNA_RESERVE_MODEL;
use crate::status::format_credit_micros;
use crate::status::format_estimated_usd_micros;
use crate::status::format_tokens_compact;
use crate::terminal_program_status::ProgramStatus;
use codex_app_server_protocol::AskForApproval;
use codex_config::ConfigLayerSource;
use codex_config::os_host_name;
@@ -322,18 +322,18 @@ impl ChatWidget {
self.bottom_pane.terminal_title_requires_action()
}
pub(super) fn desired_iterm_session_status(&self) -> ItermSessionStatus {
pub(super) fn desired_program_status(&self) -> ProgramStatus {
if self.terminal_title_requires_action() {
ItermSessionStatus::Waiting
ProgramStatus::Blocked
} else if self.bottom_pane.is_task_running() || !self.unified_exec_processes.is_empty() {
ItermSessionStatus::Working
ProgramStatus::Working
} else {
ItermSessionStatus::Idle
ProgramStatus::Idle
}
}
pub(super) fn iterm_session_detail(&self, status: ItermSessionStatus) -> Option<&str> {
(status == ItermSessionStatus::Working && self.bottom_pane.is_task_running())
pub(super) fn iterm_session_detail(&self, status: ProgramStatus) -> Option<&str> {
(status == ProgramStatus::Working && self.bottom_pane.is_task_running())
.then_some(self.status_state.current_status.header.as_str())
.filter(|detail| *detail != "Working")
}
+2 -2
View File
@@ -257,8 +257,6 @@ mod history_projection;
mod history_replay;
#[path = "tests/home_cleanup_tests.rs"]
mod home_cleanup_tests;
#[path = "tests/iterm_session_status.rs"]
mod iterm_session_status;
#[path = "tests/luna_reserve_usage_tests.rs"]
mod luna_reserve_usage_tests;
mod mcp_startup;
@@ -299,6 +297,8 @@ mod status_command_tests;
mod status_surface_previews;
#[path = "tests/subagent_activity_tests.rs"]
mod subagent_activity;
#[path = "tests/terminal_program_status.rs"]
mod terminal_program_status;
mod terminal_title;
#[path = "tests/tool_activity_tests.rs"]
mod tool_activity_tests;
@@ -1,16 +1,17 @@
use super::*;
use crate::iterm_session_status::ItermSessionStatus::Idle;
use crate::iterm_session_status::ItermSessionStatus::Waiting;
use crate::iterm_session_status::ItermSessionStatus::Working;
use pretty_assertions::assert_eq;
use crate::terminal_program_status::ProgramStatus::Blocked;
use crate::terminal_program_status::ProgramStatus::Idle;
use crate::terminal_program_status::ProgramStatus::Working;
#[tokio::test]
async fn session_status_follows_existing_lifecycle_state() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
let mut states = vec![chat.desired_iterm_session_status()];
let mut states = vec![chat.desired_program_status()];
chat.bottom_pane.set_task_running(/*running*/ true);
states.push(chat.desired_iterm_session_status());
states.push(chat.desired_program_status());
handle_exec_approval_request(
&mut chat,
@@ -31,13 +32,13 @@ async fn session_status_follows_existing_lifecycle_state() {
available_decisions: None,
},
);
states.push(chat.desired_iterm_session_status());
states.push(chat.desired_program_status());
chat.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));
states.push(chat.desired_iterm_session_status());
states.push(chat.desired_program_status());
chat.bottom_pane.set_task_running(/*running*/ false);
states.push(chat.desired_iterm_session_status());
states.push(chat.desired_program_status());
chat.unified_exec_processes.push(UnifiedExecProcessSummary {
key: "background-process".to_string(),
@@ -45,13 +46,13 @@ async fn session_status_follows_existing_lifecycle_state() {
command_display: "sleep 5".to_string(),
recent_chunks: Vec::new(),
});
states.push(chat.desired_iterm_session_status());
states.push(chat.desired_program_status());
chat.unified_exec_processes.clear();
states.push(chat.desired_iterm_session_status());
states.push(chat.desired_program_status());
assert_eq!(
states,
[Idle, Working, Waiting, Working, Idle, Working, Idle]
[Idle, Working, Blocked, Working, Idle, Working, Idle]
);
}
@@ -65,7 +66,7 @@ async fn session_status_detail_uses_only_foreground_activity() {
let generic = detail(&chat, Working);
chat.set_status_header("Reading source".to_string());
let working = detail(&chat, Working);
let waiting = detail(&chat, Waiting);
let waiting = detail(&chat, Blocked);
chat.bottom_pane.set_task_running(/*running*/ false);
let idle = detail(&chat, Idle);
let background = detail(&chat, Working);
+106 -56
View File
@@ -6,26 +6,31 @@ use codex_terminal_detection::TerminalName;
use crossterm::Command;
use pretty_assertions::assert_eq;
use super::ClearItermSessionStatus;
use super::ItermSessionStatus;
use super::ClearTerminalProgramStatus;
use super::NO_EMITTED_STATUS;
use super::ProgramStatus;
use super::SessionStatusState;
use super::SetItermSessionStatus;
use super::iterm_session_status_supported;
use super::SetTerminalProgramStatus;
use super::TerminalProgramStatusSupport;
use super::sanitize_iterm_session_detail;
use super::terminal_program_status_support;
#[test]
fn encodes_session_status_fields() {
fn encodes_iterm_session_status_fields() {
let mut encoded = Vec::new();
for (status, detail) in [
(ItermSessionStatus::Idle, ""),
(ItermSessionStatus::Working, "Reading source"),
(ItermSessionStatus::Waiting, ""),
(ProgramStatus::Idle, ""),
(ProgramStatus::Working, "Reading source"),
(ProgramStatus::Blocked, ""),
] {
let mut value = String::new();
SetItermSessionStatus(status, detail)
.write_ansi(&mut value)
.expect("encode iTerm2 session status");
SetTerminalProgramStatus {
status,
iterm_detail: detail,
emit_iterm_status: true,
}
.write_ansi(&mut value)
.expect("encode combined terminal program status");
encoded.push(value);
}
@@ -37,6 +42,20 @@ fn encodes_session_status_fields() {
insta::assert_snapshot!("iterm_session_status_fields", snapshot);
}
#[test]
fn encodes_generic_program_status() {
let mut encoded = String::new();
SetTerminalProgramStatus {
status: ProgramStatus::Working,
iterm_detail: "ignored",
emit_iterm_status: false,
}
.write_ansi(&mut encoded)
.expect("encode generic terminal program status");
assert_eq!(encoded, "\x1b]7501;state=working:app=codex\x1b\\");
}
#[test]
fn detail_is_safe_bounded_osc_text() {
assert_eq!(
@@ -51,15 +70,24 @@ fn detail_is_safe_bounded_osc_text() {
#[test]
fn clear_empties_every_owned_field() {
let mut encoded = String::new();
ClearItermSessionStatus
.write_ansi(&mut encoded)
.expect("encode iTerm2 session status clear");
let mut combined = String::new();
ClearTerminalProgramStatus {
emit_iterm_status: true,
}
.write_ansi(&mut combined)
.expect("encode combined terminal program status clear");
let mut generic = String::new();
ClearTerminalProgramStatus {
emit_iterm_status: false,
}
.write_ansi(&mut generic)
.expect("encode generic terminal program status clear");
assert_eq!(
encoded,
"\x1b]21337;status=;indicator=;status-color=;detail=\x07"
combined,
"\x1b]21337;status=;indicator=;status-color=;detail=\x07\x1b]7501;state=clear\x1b\\"
);
assert_eq!(generic, "\x1b]7501;state=clear\x1b\\");
}
#[test]
@@ -67,33 +95,33 @@ fn cache_changes_only_after_a_successful_write() {
let state = SessionStatusState::new();
let mut emitted = Vec::new();
state
.update(ItermSessionStatus::Working as u8, "Thinking", || {
.update(ProgramStatus::Working as u8, "Thinking", || {
Err(io::Error::other("write failed"))
})
.expect_err("failed write should be returned");
state
.update(ItermSessionStatus::Working as u8, "Thinking", || {
emitted.push(ItermSessionStatus::Working as u8);
.update(ProgramStatus::Working as u8, "Thinking", || {
emitted.push(ProgramStatus::Working as u8);
Ok(())
})
.expect("retry status write");
state
.update(ItermSessionStatus::Working as u8, "Thinking", || {
emitted.push(ItermSessionStatus::Waiting as u8);
.update(ProgramStatus::Working as u8, "Thinking", || {
emitted.push(ProgramStatus::Blocked as u8);
Ok(())
})
.expect("deduplicate status write");
state
.update(ItermSessionStatus::Working as u8, "Reading", || {
emitted.push(ItermSessionStatus::Working as u8);
.update(ProgramStatus::Working as u8, "Reading", || {
emitted.push(ProgramStatus::Working as u8);
Ok(())
})
.expect("emit changed detail");
state.invalidate();
state
.update(ItermSessionStatus::Working as u8, "Thinking", || {
emitted.push(ItermSessionStatus::Working as u8);
.update(ProgramStatus::Working as u8, "Thinking", || {
emitted.push(ProgramStatus::Working as u8);
Ok(())
})
.expect("re-emit status after terminal handoff");
@@ -107,16 +135,16 @@ fn cache_changes_only_after_a_successful_write() {
assert_eq!(
emitted,
[
ItermSessionStatus::Working as u8,
ItermSessionStatus::Working as u8,
ItermSessionStatus::Working as u8,
ProgramStatus::Working as u8,
ProgramStatus::Working as u8,
ProgramStatus::Working as u8,
NO_EMITTED_STATUS,
]
);
}
#[test]
fn supports_only_direct_iterm_ttys() {
fn selects_protocols_for_terminal_environment() {
let direct_iterm = TerminalInfo {
name: TerminalName::Iterm2,
term_program: Some("iTerm.app".to_string()),
@@ -124,36 +152,58 @@ fn supports_only_direct_iterm_ttys() {
term: None,
multiplexer: None,
};
assert!(iterm_session_status_supported(
&direct_iterm,
/*stdout_is_terminal*/ true,
/*screen_active*/ false,
));
assert!(!iterm_session_status_supported(
&direct_iterm,
/*stdout_is_terminal*/ false,
/*screen_active*/ false,
));
assert!(!iterm_session_status_supported(
&direct_iterm,
/*stdout_is_terminal*/ true,
/*screen_active*/ true,
));
let both_protocols = TerminalProgramStatusSupport {
osc_7501: true,
osc_21337: true,
};
let no_protocols = TerminalProgramStatusSupport {
osc_7501: false,
osc_21337: false,
};
let generic_protocol = TerminalProgramStatusSupport {
osc_7501: true,
osc_21337: false,
};
assert_eq!(
terminal_program_status_support(
&direct_iterm,
/*stdout_is_terminal*/ true,
/*screen_active*/ false,
),
both_protocols
);
assert_eq!(
terminal_program_status_support(
&direct_iterm,
/*stdout_is_terminal*/ false,
/*screen_active*/ false,
),
no_protocols
);
assert!(!iterm_session_status_supported(
&TerminalInfo {
for terminal in [
TerminalInfo {
multiplexer: Some(Multiplexer::Tmux { version: None }),
..direct_iterm.clone()
},
/*stdout_is_terminal*/ true,
/*screen_active*/ false,
));
assert!(!iterm_session_status_supported(
&TerminalInfo {
TerminalInfo {
name: TerminalName::Ghostty,
..direct_iterm
..direct_iterm.clone()
},
/*stdout_is_terminal*/ true,
/*screen_active*/ false,
));
] {
assert_eq!(
terminal_program_status_support(
&terminal, /*stdout_is_terminal*/ true, /*screen_active*/ false,
),
generic_protocol
);
}
assert_eq!(
terminal_program_status_support(
&direct_iterm,
/*stdout_is_terminal*/ true,
/*screen_active*/ true,
),
generic_protocol
);
}
+1 -1
View File
@@ -162,9 +162,9 @@ mod hooks_rpc;
mod ide_context;
mod inline_visualization;
pub(crate) mod insert_history;
mod iterm_session_status;
mod managed_worktree_tool_specs;
mod managed_worktree_tools;
mod terminal_program_status;
pub use insert_history::insert_history_lines;
mod footer_hint;
mod key_hint;
@@ -1,8 +1,8 @@
---
source: tui/src/iterm_session_status_tests.rs
assertion_line: 39
assertion_line: 42
expression: snapshot
---
\u{1b}]21337;status=Idle;indicator=#00d75f;status-color=;detail=\u{7}
\u{1b}]21337;status=Working;indicator=#ff9500;status-color=;detail=Reading source\u{7}
\u{1b}]21337;status=Waiting;indicator=#5f87ff;status-color=;detail=\u{7}
\u{1b}]21337;status=Idle;indicator=#00d75f;status-color=;detail=\u{7}\u{1b}]7501;state=idle:app=codex\u{1b}\\
\u{1b}]21337;status=Working;indicator=#ff9500;status-color=;detail=Reading source\u{7}\u{1b}]7501;state=working:app=codex\u{1b}\\
\u{1b}]21337;status=Waiting;indicator=#5f87ff;status-color=;detail=\u{7}\u{1b}]7501;state=blocked:app=codex\u{1b}\\
@@ -1,9 +1,10 @@
//! Emits coarse Codex lifecycle state through iTerm2's OSC 21337 protocol.
//! Emits coarse Codex lifecycle state through terminal status protocols.
//!
//! This first integration is intentionally limited to direct iTerm2 sessions.
//! The payload includes bounded activity text from the existing TUI status.
//! A process-wide cache matches the terminal session's ownership and prevents
//! stale state when the active chat changes.
//! OSC 7501 is safe to send to any terminal and reports only the lifecycle
//! state and app name. Direct iTerm2 sessions also receive the existing OSC
//! 21337 status, including bounded activity text. A process-wide cache matches
//! the terminal session's ownership and prevents stale state when the active
//! chat changes.
use std::fmt;
use std::io;
@@ -21,7 +22,7 @@ use ratatui::crossterm::execute;
const NO_EMITTED_STATUS: u8 = 0;
const INVALIDATED_EMITTED_STATUS: u8 = u8::MAX;
static SESSION_STATUS_STATE: SessionStatusState = SessionStatusState::new();
static ITERM_SESSION_STATUS_SUPPORTED: OnceLock<bool> = OnceLock::new();
static TERMINAL_PROGRAM_STATUS_SUPPORT: OnceLock<TerminalProgramStatusSupport> = OnceLock::new();
struct SessionStatusState {
last_emitted: Mutex<EmittedSessionStatus>,
@@ -75,58 +76,92 @@ impl SessionStatusState {
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub(crate) enum ItermSessionStatus {
Idle = 1,
Working = 2,
Waiting = 3,
struct TerminalProgramStatusSupport {
osc_7501: bool,
osc_21337: bool,
}
impl ItermSessionStatus {
fn label(self) -> &'static str {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub(crate) enum ProgramStatus {
Idle = 1,
Working = 2,
Blocked = 3,
}
impl ProgramStatus {
fn iterm_label(self) -> &'static str {
match self {
Self::Idle => "Idle",
Self::Working => "Working",
Self::Waiting => "Waiting",
Self::Blocked => "Waiting",
}
}
fn indicator(self) -> &'static str {
fn iterm_indicator(self) -> &'static str {
match self {
Self::Idle => "#00d75f",
Self::Working => "#ff9500",
Self::Waiting => "#5f87ff",
Self::Blocked => "#5f87ff",
}
}
fn protocol_state(self) -> &'static str {
match self {
Self::Idle => "idle",
Self::Working => "working",
Self::Blocked => "blocked",
}
}
}
pub(crate) fn set_iterm_session_status(
status: ItermSessionStatus,
pub(crate) fn set_terminal_program_status(
status: ProgramStatus,
detail: Option<&str>,
) -> io::Result<()> {
if cfg!(test) {
return Ok(());
}
if !*ITERM_SESSION_STATUS_SUPPORTED.get_or_init(|| {
iterm_session_status_supported(
&terminal_info(),
/*stdout_is_terminal*/ stdout().is_terminal(),
/*screen_active*/ std::env::var_os("STY").is_some(),
)
}) {
let support = current_terminal_program_status_support();
if !support.osc_7501 {
return Ok(());
}
let detail = sanitize_iterm_session_detail(detail.unwrap_or_default());
let detail = if support.osc_21337 {
sanitize_iterm_session_detail(detail.unwrap_or_default())
} else {
String::new()
};
SESSION_STATUS_STATE.update(status as u8, &detail, || {
execute!(stdout(), SetItermSessionStatus(status, &detail))
execute!(
stdout(),
SetTerminalProgramStatus {
status,
iterm_detail: &detail,
emit_iterm_status: support.osc_21337,
}
)
})
}
pub(crate) fn clear_iterm_session_status() -> io::Result<()> {
pub(crate) fn clear_terminal_program_status() -> io::Result<()> {
if cfg!(test) {
return Ok(());
}
let support = current_terminal_program_status_support();
if !support.osc_7501 {
return Ok(());
}
SESSION_STATUS_STATE.update(NO_EMITTED_STATUS, "", || {
execute!(stdout(), ClearItermSessionStatus)
execute!(
stdout(),
ClearTerminalProgramStatus {
emit_iterm_status: support.osc_21337,
}
)
})
}
@@ -134,7 +169,7 @@ pub(crate) fn clear_iterm_session_status() -> io::Result<()> {
///
/// The next active draw will re-emit Codex's current status even if its
/// lifecycle state did not change during the handoff.
pub(crate) fn invalidate_iterm_session_status() {
pub(crate) fn invalidate_terminal_program_status() {
SESSION_STATUS_STATE.invalidate();
}
@@ -153,35 +188,60 @@ fn sanitize_iterm_session_detail(detail: &str) -> String {
escaped
}
fn iterm_session_status_supported(
fn current_terminal_program_status_support() -> TerminalProgramStatusSupport {
*TERMINAL_PROGRAM_STATUS_SUPPORT.get_or_init(|| {
terminal_program_status_support(
&terminal_info(),
/*stdout_is_terminal*/ stdout().is_terminal(),
/*screen_active*/ std::env::var_os("STY").is_some(),
)
})
}
fn terminal_program_status_support(
terminal: &TerminalInfo,
stdout_is_terminal: bool,
screen_active: bool,
) -> bool {
stdout_is_terminal
) -> TerminalProgramStatusSupport {
let osc_21337 = stdout_is_terminal
&& terminal.name == TerminalName::Iterm2
&& terminal.multiplexer.is_none()
&& !screen_active
&& !screen_active;
TerminalProgramStatusSupport {
osc_7501: stdout_is_terminal,
osc_21337,
}
}
#[derive(Clone, Copy, Debug)]
struct SetItermSessionStatus<'a>(ItermSessionStatus, &'a str);
struct SetTerminalProgramStatus<'a> {
status: ProgramStatus,
iterm_detail: &'a str,
emit_iterm_status: bool,
}
impl Command for SetItermSessionStatus<'_> {
impl Command for SetTerminalProgramStatus<'_> {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
if self.emit_iterm_status {
write!(
f,
"\x1b]21337;status={};indicator={};status-color=;detail={}\x07",
self.status.iterm_label(),
self.status.iterm_indicator(),
self.iterm_detail,
)?;
}
write!(
f,
"\x1b]21337;status={};indicator={};status-color=;detail={}\x07",
self.0.label(),
self.0.indicator(),
self.1,
"\x1b]7501;state={}:app=codex\x1b\\",
self.status.protocol_state(),
)
}
#[cfg(windows)]
fn execute_winapi(&self) -> io::Result<()> {
Err(io::Error::other(
"tried to set iTerm2 session status using WinAPI; use ANSI instead",
"tried to set terminal program status using WinAPI; use ANSI instead",
))
}
@@ -192,17 +252,22 @@ impl Command for SetItermSessionStatus<'_> {
}
#[derive(Clone, Copy, Debug)]
struct ClearItermSessionStatus;
struct ClearTerminalProgramStatus {
emit_iterm_status: bool,
}
impl Command for ClearItermSessionStatus {
impl Command for ClearTerminalProgramStatus {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, "\x1b]21337;status=;indicator=;status-color=;detail=\x07")
if self.emit_iterm_status {
write!(f, "\x1b]21337;status=;indicator=;status-color=;detail=\x07")?;
}
write!(f, "\x1b]7501;state=clear\x1b\\")
}
#[cfg(windows)]
fn execute_winapi(&self) -> io::Result<()> {
Err(io::Error::other(
"tried to clear iTerm2 session status using WinAPI; use ANSI instead",
"tried to clear terminal program status using WinAPI; use ANSI instead",
))
}
+5 -2
View File
@@ -408,7 +408,7 @@ pub fn restore_after_exit() -> Result<()> {
if let Err(err) = terminal_stderr::finish() {
first_error.get_or_insert(err);
}
if let Err(err) = crate::iterm_session_status::clear_iterm_session_status() {
if let Err(err) = crate::terminal_program_status::clear_terminal_program_status() {
first_error.get_or_insert(err);
}
@@ -976,6 +976,9 @@ impl Tui {
if let Err(err) = terminal_stderr::pause() {
tracing::warn!("failed to restore terminal stderr before external program: {err}");
}
if let Err(err) = crate::terminal_program_status::clear_terminal_program_status() {
tracing::debug!(error = %err, "failed to clear terminal program status before external program");
}
let output = f().await;
@@ -999,7 +1002,7 @@ impl Tui {
let _ = self.enter_alt_screen();
}
crate::iterm_session_status::invalidate_iterm_session_status();
crate::terminal_program_status::invalidate_terminal_program_status();
self.resume_events();
self.schedule_screen_size_recheck(Duration::ZERO);
output
+3 -3
View File
@@ -69,11 +69,11 @@ impl SuspendContext {
}
let y = self.suspend_cursor_y.load(Ordering::Relaxed);
let _ = execute!(stdout(), MoveTo(0, y), Show);
if let Err(err) = crate::iterm_session_status::clear_iterm_session_status() {
tracing::debug!(error = %err, "failed to clear iTerm2 session status before suspend");
if let Err(err) = crate::terminal_program_status::clear_terminal_program_status() {
tracing::debug!(error = %err, "failed to clear terminal program status before suspend");
}
let suspend_result = suspend_process();
crate::iterm_session_status::invalidate_iterm_session_status();
crate::terminal_program_status::invalidate_terminal_program_status();
suspend_result?;
super::reapply_raw_mode_after_resume()?;