From c5051933f812a07e8014cd11dde2e2caac3af84c Mon Sep 17 00:00:00 2001 From: Can Celik Date: Fri, 28 Aug 2026 18:03:07 +0300 Subject: [PATCH] fix: ignore cursor events in claude hooks (#3342) refs #2832 --- docs/next/CHANGELOG.md | 1 + .../assets/claude/herdr-agent-state.ps1 | 6 +++-- .../assets/claude/herdr-agent-state.sh | 11 ++++---- src/integration/mod.rs | 2 +- src/integration/tests.rs | 4 +-- tests/cli/hooks.rs | 26 +++++++++++++++++++ 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 886ffca8..8a3ffe37 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -6,6 +6,7 @@ - Custom themes can now define separate light and dark color overrides when automatic theme switching is enabled. (#837, thanks @aneym) ### Fixed +- Claude Code integration hooks now ignore Cursor CLI's Claude-compatible session events, preventing Cursor sessions from being stored as resumable Claude sessions. (#2832) - Running named servers now activate remote agent-detection manifests downloaded by another server, preventing stale agent states and `agent explain` output until restart. (#2711) - New lifecycle event subscriptions now stream only events emitted after subscription begins instead of replaying retained history. (#1270) - Windows users whose endpoint security blocks the fileless PowerShell install command can now use a local `install.cmd` bootstrap; installer downloads use `curl.exe` while preserving package checksum verification. (#2751) diff --git a/src/integration/assets/claude/herdr-agent-state.ps1 b/src/integration/assets/claude/herdr-agent-state.ps1 index d82a6fe3..fc596865 100644 --- a/src/integration/assets/claude/herdr-agent-state.ps1 +++ b/src/integration/assets/claude/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=claude -# HERDR_INTEGRATION_VERSION=8 +# HERDR_INTEGRATION_VERSION=9 param([string]$Action = "") @@ -17,8 +17,10 @@ try { exit 0 } +$propertyNames = @($payload.PSObject.Properties.Name) +if ((Test-Path Env:CURSOR_VERSION) -or $propertyNames -ccontains "cursor_version") { exit 0 } +if (-not ($propertyNames -ccontains "hook_event_name") -or $payload.hook_event_name -isnot [string] -or $payload.hook_event_name -cne "SessionStart") { exit 0 } if (-not [string]::IsNullOrWhiteSpace($payload.agent_id)) { exit 0 } -if ($payload.hook_event_name -eq "SubagentStop") { exit 0 } $sessionId = $payload.session_id if ([string]::IsNullOrWhiteSpace($sessionId)) { exit 0 } diff --git a/src/integration/assets/claude/herdr-agent-state.sh b/src/integration/assets/claude/herdr-agent-state.sh index 9b9a7dfd..5d11a80c 100644 --- a/src/integration/assets/claude/herdr-agent-state.sh +++ b/src/integration/assets/claude/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=claude -# HERDR_INTEGRATION_VERSION=8 +# HERDR_INTEGRATION_VERSION=9 set -eu @@ -48,15 +48,14 @@ if hook_input_file: except Exception: hook_input = {} +if "CURSOR_VERSION" in os.environ or "cursor_version" in hook_input: + raise SystemExit(0) hook_event_name = str(hook_input.get("hook_event_name") or "") +if hook_event_name != "SessionStart": + raise SystemExit(0) is_subagent = bool(hook_input.get("agent_id")) if is_subagent: raise SystemExit(0) -if hook_event_name == "SubagentStop": - # SubagentStop is a completion event. Older Herdr integrations mapped it - # to durable working, but Claude recap/away-summary can emit it after the - # main turn has already stopped. Never let it revive an idle pane. - raise SystemExit(0) request_id = f"{source}:{int(time.time() * 1000)}:{random.randrange(1_000_000):06d}" report_seq = time.time_ns() session_id = hook_input.get("session_id") diff --git a/src/integration/mod.rs b/src/integration/mod.rs index 33bb163d..4085b447 100644 --- a/src/integration/mod.rs +++ b/src/integration/mod.rs @@ -38,7 +38,7 @@ const CLAUDE_HOOK_ASSET: &str = if cfg!(windows) { } else { include_str!("assets/claude/herdr-agent-state.sh") }; -const CLAUDE_INTEGRATION_VERSION: u32 = 8; +const CLAUDE_INTEGRATION_VERSION: u32 = 9; const CODEX_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 d54183f2..7cac9145 100644 --- a/src/integration/tests.rs +++ b/src/integration/tests.rs @@ -1060,7 +1060,7 @@ fn claude_v1_integration_status_is_outdated() { assert_eq!(claude.path, hook_path); assert_eq!(claude.installed_version, Some(1)); - assert_eq!(claude.expected_version, 8); + assert_eq!(claude.expected_version, 9); assert_eq!(claude.state, IntegrationStatusKind::Outdated); std::env::remove_var("HOME"); @@ -1090,7 +1090,7 @@ fn claude_v2_integration_status_is_outdated() { assert_eq!(claude.path, hook_path); assert_eq!(claude.installed_version, Some(2)); - assert_eq!(claude.expected_version, 8); + assert_eq!(claude.expected_version, 9); assert_eq!(claude.state, IntegrationStatusKind::Outdated); std::env::remove_var("HOME"); diff --git a/tests/cli/hooks.rs b/tests/cli/hooks.rs index 21e7036b..de2bb271 100644 --- a/tests/cli/hooks.rs +++ b/tests/cli/hooks.rs @@ -84,6 +84,7 @@ fn run_shell_hook_with_env( .env("HERDR_SOCKET_PATH", &socket_path) .env("HERDR_PANE_ID", "p_test") .env_remove("CODEX_THREAD_ID") + .env_remove("CURSOR_VERSION") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); @@ -150,6 +151,31 @@ fn claude_hook_reports_session_id_from_stdin() { assert!(request["params"].get("state").is_none()); } +#[test] +fn claude_hook_ignores_cursor_compatibility_payloads() { + assert!(run_claude_hook( + "session", + r#"{"hook_event_name":"sessionStart","session_id":"cursor-session"}"#, + ) + .is_none()); + + assert!(run_claude_hook( + "session", + r#"{"hook_event_name":"SessionStart","session_id":"cursor-session","cursor_version":"2026.08.11-e8db854"}"#, + ) + .is_none()); + + for cursor_version in ["2026.08.11-e8db854", ""] { + assert!(run_shell_hook_with_env( + "src/integration/assets/claude/herdr-agent-state.sh", + &["session"], + r#"{"hook_event_name":"SessionStart","session_id":"cursor-session"}"#, + &[("CURSOR_VERSION", cursor_version)], + ) + .is_none()); + } +} + #[test] fn codex_hook_reports_persisted_root_session_and_ignores_ephemeral_or_nested_sessions() { let request = run_codex_hook(