diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-19 20:59:30 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-19 21:07:52 +0300 |
| commit | 2939030cec3d7b190d60c34c156552dfc0c06136 (patch) | |
| tree | 3de0c6468b2481ee8922f63c431c621e6f82de01 /src/integration | |
| parent | 1bef56406a8602b73d3d5d8e71186dddb6804c80 (diff) | |
fix: stop claude post-tool hooks pinning working
refs #198
Diffstat (limited to 'src/integration')
| -rw-r--r-- | src/integration/assets/claude/herdr-agent-state.sh | 2 | ||||
| -rw-r--r-- | src/integration/mod.rs | 120 |
2 files changed, 89 insertions, 33 deletions
diff --git a/src/integration/assets/claude/herdr-agent-state.sh b/src/integration/assets/claude/herdr-agent-state.sh index 18b042e8..035c3fe0 100644 --- a/src/integration/assets/claude/herdr-agent-state.sh +++ b/src/integration/assets/claude/herdr-agent-state.sh @@ -2,7 +2,7 @@ # installed by herdr # safe to edit. this hook only activates inside herdr-managed panes. # HERDR_INTEGRATION_ID=claude -# HERDR_INTEGRATION_VERSION=1 +# HERDR_INTEGRATION_VERSION=2 set -eu diff --git a/src/integration/mod.rs b/src/integration/mod.rs index 309d81f7..7e418497 100644 --- a/src/integration/mod.rs +++ b/src/integration/mod.rs @@ -16,7 +16,7 @@ const PI_INTEGRATION_VERSION: u32 = 1; const PI_CODING_AGENT_DIR_ENV_VAR: &str = "PI_CODING_AGENT_DIR"; const CLAUDE_HOOK_INSTALL_NAME: &str = "herdr-agent-state.sh"; const CLAUDE_HOOK_ASSET: &str = include_str!("assets/claude/herdr-agent-state.sh"); -const CLAUDE_INTEGRATION_VERSION: u32 = 1; +const CLAUDE_INTEGRATION_VERSION: u32 = 2; const CLAUDE_CONFIG_DIR_ENV_VAR: &str = "CLAUDE_CONFIG_DIR"; const CODEX_HOOK_INSTALL_NAME: &str = "herdr-agent-state.sh"; const CODEX_HOOK_ASSET: &str = include_str!("assets/codex/herdr-agent-state.sh"); @@ -495,6 +495,16 @@ pub(crate) fn install_claude() -> io::Result<ClaudeInstallPaths> { "claude settings hooks", )?; let quoted_hook_path = shell_single_quote(&hook_path.display().to_string()); + remove_command_hook( + hooks, + "PostToolUse", + &format!("bash {quoted_hook_path} working"), + )?; + remove_command_hook( + hooks, + "PostToolUseFailure", + &format!("bash {quoted_hook_path} working"), + )?; ensure_command_hook( hooks, "UserPromptSubmit", @@ -518,20 +528,6 @@ pub(crate) fn install_claude() -> io::Result<ClaudeInstallPaths> { )?; ensure_command_hook( hooks, - "PostToolUse", - format!("bash {quoted_hook_path} working"), - 10, - Some("*"), - )?; - ensure_command_hook( - hooks, - "PostToolUseFailure", - format!("bash {quoted_hook_path} working"), - 10, - Some("*"), - )?; - ensure_command_hook( - hooks, "SubagentStop", format!("bash {quoted_hook_path} working"), 10, @@ -1532,16 +1528,8 @@ mod tests { .unwrap() .contains(" blocked") ); - assert!(settings["hooks"]["PostToolUse"][0]["hooks"][0]["command"] - .as_str() - .unwrap() - .contains(" working")); - assert!( - settings["hooks"]["PostToolUseFailure"][0]["hooks"][0]["command"] - .as_str() - .unwrap() - .contains(" working") - ); + assert!(settings["hooks"].get("PostToolUse").is_none()); + assert!(settings["hooks"].get("PostToolUseFailure").is_none()); assert!(settings["hooks"]["SubagentStop"][0]["hooks"][0]["command"] .as_str() .unwrap() @@ -1609,23 +1597,91 @@ mod tests { .len(), 1 ); + assert!(settings["hooks"].get("PostToolUse").is_none()); + assert!(settings["hooks"].get("PostToolUseFailure").is_none()); assert_eq!( - settings["hooks"]["PostToolUse"].as_array().unwrap().len(), + settings["hooks"]["SubagentStop"].as_array().unwrap().len(), 1 ); + assert_eq!(settings["hooks"]["Stop"].as_array().unwrap().len(), 1); + assert_eq!(settings["hooks"]["SessionEnd"].as_array().unwrap().len(), 1); + + std::env::remove_var("HOME"); + let _ = fs::remove_dir_all(base); + } + + #[test] + fn install_claude_removes_deprecated_post_tool_hooks_and_preserves_user_hooks() { + let _lock = integration_env_lock(); + let base = unique_base(); + let home = base.join("home"); + let claude_dir = home.join(".claude"); + let hooks_dir = claude_dir.join("hooks"); + fs::create_dir_all(&hooks_dir).unwrap(); + let hook_path = hooks_dir.join(CLAUDE_HOOK_INSTALL_NAME); + fs::write( + claude_dir.join("settings.json"), + format!( + r#"{{"hooks":{{"PostToolUse":[{{"matcher":"*","hooks":[{{"type":"command","command":"bash '{}' working","timeout":10}},{{"type":"command","command":"echo keep-post","timeout":10}}]}}],"PostToolUseFailure":[{{"matcher":"*","hooks":[{{"type":"command","command":"bash '{}' working","timeout":10}},{{"type":"command","command":"echo keep-failure","timeout":10}}]}}]}}}}"#, + hook_path.display(), + hook_path.display(), + ), + ) + .unwrap(); + std::env::set_var("HOME", &home); + + install_claude().unwrap(); + + let settings: Value = + serde_json::from_str(&fs::read_to_string(claude_dir.join("settings.json")).unwrap()) + .unwrap(); + assert_eq!( + settings["hooks"]["PostToolUse"][0]["hooks"][0]["command"], + "echo keep-post" + ); assert_eq!( - settings["hooks"]["PostToolUseFailure"] + settings["hooks"]["PostToolUseFailure"][0]["hooks"][0]["command"], + "echo keep-failure" + ); + assert_eq!( + settings["hooks"]["UserPromptSubmit"] .as_array() .unwrap() .len(), 1 ); - assert_eq!( - settings["hooks"]["SubagentStop"].as_array().unwrap().len(), - 1 - ); + assert_eq!(settings["hooks"]["PreToolUse"].as_array().unwrap().len(), 1); assert_eq!(settings["hooks"]["Stop"].as_array().unwrap().len(), 1); - assert_eq!(settings["hooks"]["SessionEnd"].as_array().unwrap().len(), 1); + + std::env::remove_var("HOME"); + let _ = fs::remove_dir_all(base); + } + + #[test] + fn claude_v1_integration_status_is_outdated() { + let _lock = integration_env_lock(); + let base = unique_base(); + let home = base.join("home"); + let claude_hooks_dir = home.join(".claude").join("hooks"); + fs::create_dir_all(&claude_hooks_dir).unwrap(); + let hook_path = claude_hooks_dir.join(CLAUDE_HOOK_INSTALL_NAME); + fs::write( + &hook_path, + "#!/bin/sh\n# HERDR_INTEGRATION_ID=claude\n# HERDR_INTEGRATION_VERSION=1\n", + ) + .unwrap(); + std::env::set_var("HOME", &home); + + let statuses = installed_integration_statuses(); + let claude = statuses + .iter() + .find(|status| status.target == crate::api::schema::IntegrationTarget::Claude) + .unwrap(); + + assert_eq!(claude.path, hook_path); + assert_eq!(claude.installed_version, Some(1)); + assert_eq!(claude.expected_version, 2); + assert_eq!(claude.state, IntegrationStatusKind::Outdated); std::env::remove_var("HOME"); let _ = fs::remove_dir_all(base); |
