From 1491b7dd9c992ef0ad2b763f3e450befaf25c47f Mon Sep 17 00:00:00 2001 From: JJ Liebig Date: Tue, 28 Jul 2026 03:13:35 +0200 Subject: [PATCH] fix: ignore nested codex session reports (#1927) * fix: ignore nested codex session reports refs #1789 * test: cover matching codex thread id --------- Co-authored-by: Can Celik --- .../assets/codex/herdr-agent-state.ps1 | 4 ++- .../assets/codex/herdr-agent-state.sh | 8 ++++- src/integration/mod.rs | 2 +- src/integration/tests.rs | 3 +- src/pane.rs | 11 +++++++ tests/cli/hooks.rs | 31 +++++++++++++++++-- 6 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/integration/assets/codex/herdr-agent-state.ps1 b/src/integration/assets/codex/herdr-agent-state.ps1 index d837b78a..b578e7b9 100644 --- a/src/integration/assets/codex/herdr-agent-state.ps1 +++ b/src/integration/assets/codex/herdr-agent-state.ps1 @@ -2,7 +2,7 @@ # managed by herdr; reinstalling or updating the integration overwrites this file. # add custom hooks beside this file instead of editing it. # HERDR_INTEGRATION_ID=codex -# HERDR_INTEGRATION_VERSION=6 +# HERDR_INTEGRATION_VERSION=7 param([string]$Action = "") @@ -21,6 +21,8 @@ if ($payload.hook_event_name -and $payload.hook_event_name -ne "SessionStart") { $sessionId = $payload.session_id if ([string]::IsNullOrWhiteSpace($sessionId)) { exit 0 } +if ([string]::IsNullOrWhiteSpace($payload.transcript_path)) { exit 0 } +if (-not [string]::IsNullOrWhiteSpace($env:CODEX_THREAD_ID) -and $env:CODEX_THREAD_ID -ne $sessionId) { exit 0 } $seq = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() try { diff --git a/src/integration/assets/codex/herdr-agent-state.sh b/src/integration/assets/codex/herdr-agent-state.sh index b3b08a78..687a8181 100644 --- a/src/integration/assets/codex/herdr-agent-state.sh +++ b/src/integration/assets/codex/herdr-agent-state.sh @@ -3,7 +3,7 @@ # managed by herdr; reinstalling or updating the integration overwrites this file. # add custom hooks beside this file instead of editing it. # HERDR_INTEGRATION_ID=codex -# HERDR_INTEGRATION_VERSION=6 +# HERDR_INTEGRATION_VERSION=7 set -eu @@ -56,6 +56,12 @@ request_id = f"{source}:{int(time.time() * 1000)}:{random.randrange(1_000_000):0 report_seq = time.time_ns() session_id = hook_input.get("session_id") agent_session_id = session_id if isinstance(session_id, str) and session_id else None +transcript_path = hook_input.get("transcript_path") +if not isinstance(transcript_path, str) or not transcript_path.strip(): + raise SystemExit(0) +inherited_session_id = os.environ.get("CODEX_THREAD_ID") +if inherited_session_id and inherited_session_id != agent_session_id: + raise SystemExit(0) session_start_source = hook_input.get("source") if hook_event_name == "SessionStart" else None if not isinstance(session_start_source, str) or not session_start_source: session_start_source = None diff --git a/src/integration/mod.rs b/src/integration/mod.rs index 40189c64..77d8ddda 100644 --- a/src/integration/mod.rs +++ b/src/integration/mod.rs @@ -47,7 +47,7 @@ const CODEX_HOOK_ASSET: &str = if cfg!(windows) { } else { include_str!("assets/codex/herdr-agent-state.sh") }; -const CODEX_INTEGRATION_VERSION: u32 = 6; +const CODEX_INTEGRATION_VERSION: u32 = 7; const KIMI_HOOK_INSTALL_NAME: &str = if cfg!(windows) { "herdr-agent-state.ps1" } else { diff --git a/src/integration/tests.rs b/src/integration/tests.rs index 6e3c756c..ff616264 100644 --- a/src/integration/tests.rs +++ b/src/integration/tests.rs @@ -1238,7 +1238,7 @@ fn codex_v2_integration_status_is_outdated() { assert_eq!(codex.path, hook_path); assert_eq!(codex.installed_version, Some(2)); - assert_eq!(codex.expected_version, 6); + assert_eq!(codex.expected_version, 7); assert_eq!(codex.state, IntegrationStatusKind::Outdated); std::env::remove_var("HOME"); @@ -2709,6 +2709,7 @@ fn bundled_integration_assets_report_session_refs() { CODEX_HOOK_ASSET.contains("session_start_source") || CODEX_HOOK_ASSET.contains("--session-start-source") ); + assert!(CODEX_HOOK_ASSET.contains("CODEX_THREAD_ID")); assert!( CODEX_HOOK_ASSET.contains("pane.report_agent_session") || CODEX_HOOK_ASSET.contains("report-agent-session") diff --git a/src/pane.rs b/src/pane.rs index c6d8c003..a26dad6b 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -109,6 +109,7 @@ impl PaneLaunchEnv { } fn apply_pane_launch_env(cmd: &mut CommandBuilder, launch_env: &PaneLaunchEnv) { + cmd.env_remove("CODEX_THREAD_ID"); for (key, value) in &launch_env.extra { cmd.env(key, value); } @@ -2894,6 +2895,16 @@ impl PaneRuntime { mod tests { use super::*; + #[test] + fn pane_launch_env_removes_outer_codex_thread_id() { + let mut cmd = CommandBuilder::new("shell"); + cmd.env("CODEX_THREAD_ID", "outer-session"); + + apply_pane_launch_env(&mut cmd, &PaneLaunchEnv::default()); + + assert!(cmd.get_env("CODEX_THREAD_ID").is_none()); + } + #[tokio::test] async fn cwd_returns_accepted_report_without_rechecking_filesystem() { let stamp = std::time::SystemTime::now() diff --git a/tests/cli/hooks.rs b/tests/cli/hooks.rs index b2edd3e7..21e7036b 100644 --- a/tests/cli/hooks.rs +++ b/tests/cli/hooks.rs @@ -83,6 +83,7 @@ fn run_shell_hook_with_env( .env("HERDR_ENV", "1") .env("HERDR_SOCKET_PATH", &socket_path) .env("HERDR_PANE_ID", "p_test") + .env_remove("CODEX_THREAD_ID") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); @@ -150,16 +151,42 @@ fn claude_hook_reports_session_id_from_stdin() { } #[test] -fn codex_hook_reports_session_id_from_stdin() { +fn codex_hook_reports_persisted_root_session_and_ignores_ephemeral_or_nested_sessions() { let request = run_codex_hook( "session", - r#"{"hook_event_name":"SessionStart","session_id":"codex-session"}"#, + r#"{"hook_event_name":"SessionStart","session_id":"codex-session","transcript_path":"/tmp/codex-session.jsonl"}"#, ) .expect("codex hook should report session identity"); assert_eq!(request["method"], "pane.report_agent_session"); assert_eq!(request["params"]["agent_session_id"], "codex-session"); assert!(request["params"].get("state").is_none()); + + let matching_request = run_shell_hook_with_env( + "src/integration/assets/codex/herdr-agent-state.sh", + &["session"], + r#"{"hook_event_name":"SessionStart","session_id":"codex-session","transcript_path":"/tmp/codex-session.jsonl"}"#, + &[("CODEX_THREAD_ID", "codex-session")], + ) + .expect("matching inherited session should still report"); + assert_eq!( + matching_request["params"]["agent_session_id"], + "codex-session" + ); + + assert!(run_codex_hook( + "session", + r#"{"hook_event_name":"SessionStart","session_id":"side-session","transcript_path":null}"#, + ) + .is_none()); + + assert!(run_shell_hook_with_env( + "src/integration/assets/codex/herdr-agent-state.sh", + &["session"], + r#"{"hook_event_name":"SessionStart","session_id":"nested-session","transcript_path":"/tmp/nested-session.jsonl"}"#, + &[("CODEX_THREAD_ID", "parent-session")], + ) + .is_none()); } #[test]