Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/integration/assets/claude/herdr-agent-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ esac
[ -n "${HERDR_PANE_ID:-}" ] || exit 0
command -v python3 >/dev/null 2>&1 || exit 0

HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY'
HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY' >/dev/null 2>&1 || true
Comment thread
akbash-bot marked this conversation as resolved.
import json
import os
import random
Expand Down
2 changes: 1 addition & 1 deletion src/integration/assets/codex/herdr-agent-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ esac
[ -n "${HERDR_PANE_ID:-}" ] || exit 0
command -v python3 >/dev/null 2>&1 || exit 0

HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY'
HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY' >/dev/null 2>&1 || true
import json
import os
import random
Expand Down
2 changes: 1 addition & 1 deletion src/integration/assets/copilot/herdr-agent-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ cat >"$hook_input_file" 2>/dev/null || true
[ -n "${HERDR_PANE_ID:-}" ] || exit 0
command -v python3 >/dev/null 2>&1 || exit 0

HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY'
HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY' >/dev/null 2>&1 || true
import json
import os
import random
Expand Down
2 changes: 1 addition & 1 deletion src/integration/assets/devin/herdr-agent-state.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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=devin
# HERDR_INTEGRATION_VERSION=2
# HERDR_INTEGRATION_VERSION=3

param([string]$Action = "")

Expand Down
4 changes: 2 additions & 2 deletions src/integration/assets/devin/herdr-agent-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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=devin
# HERDR_INTEGRATION_VERSION=2
# HERDR_INTEGRATION_VERSION=3

set -eu

Expand All @@ -22,7 +22,7 @@ esac
[ -n "${HERDR_PANE_ID:-}" ] || exit 0
command -v python3 >/dev/null 2>&1 || exit 0

HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY'
HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY' >/dev/null 2>&1 || true
from __future__ import annotations

import json
Expand Down
2 changes: 1 addition & 1 deletion src/integration/assets/droid/herdr-agent-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ esac
[ -n "${HERDR_PANE_ID:-}" ] || exit 0
command -v python3 >/dev/null 2>&1 || exit 0

HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY'
HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY' >/dev/null 2>&1 || true
import json
import os
import random
Expand Down
2 changes: 1 addition & 1 deletion src/integration/assets/grok/herdr-agent-state.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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=grok
# HERDR_INTEGRATION_VERSION=1
# HERDR_INTEGRATION_VERSION=2

param([string]$Action = "")

Expand Down
4 changes: 2 additions & 2 deletions src/integration/assets/grok/herdr-agent-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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=grok
# HERDR_INTEGRATION_VERSION=1
# HERDR_INTEGRATION_VERSION=2

set -eu

Expand All @@ -22,7 +22,7 @@ esac
[ -n "${HERDR_PANE_ID:-}" ] || exit 0
command -v python3 >/dev/null 2>&1 || exit 0

HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY'
HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY' >/dev/null 2>&1 || true
import json
import os
import random
Expand Down
2 changes: 1 addition & 1 deletion src/integration/assets/mastracode/herdr-agent-state.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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=mastracode
# HERDR_INTEGRATION_VERSION=2
# HERDR_INTEGRATION_VERSION=3

param([string]$Action = "")

Expand Down
4 changes: 2 additions & 2 deletions src/integration/assets/mastracode/herdr-agent-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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=mastracode
# HERDR_INTEGRATION_VERSION=2
# HERDR_INTEGRATION_VERSION=3

set -eu

Expand All @@ -22,7 +22,7 @@ esac
[ -n "${HERDR_PANE_ID:-}" ] || exit 0
command -v python3 >/dev/null 2>&1 || exit 0

HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY'
HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY' >/dev/null 2>&1 || true
import json
import os
import random
Expand Down
6 changes: 3 additions & 3 deletions src/integration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const DEVIN_HOOK_ASSET: &str = if cfg!(windows) {
} else {
include_str!("assets/devin/herdr-agent-state.sh")
};
const DEVIN_INTEGRATION_VERSION: u32 = 2;
const DEVIN_INTEGRATION_VERSION: u32 = 3;
const DEVIN_HOOK_EVENTS: [(&str, &str); 6] = [
("SessionStart", "session"),
("UserPromptSubmit", "session"),
Expand Down Expand Up @@ -265,7 +265,7 @@ const MASTRACODE_HOOK_ASSET: &str = if cfg!(windows) {
} else {
include_str!("assets/mastracode/herdr-agent-state.sh")
};
const MASTRACODE_INTEGRATION_VERSION: u32 = 2;
const MASTRACODE_INTEGRATION_VERSION: u32 = 3;
const MASTRACODE_HOOK_TIMEOUT_MS: u64 = 10_000;
const MASTRACODE_REMOVED_HOOK_EVENTS: [(&str, &str); 2] =
[("SessionStart", "idle"), ("SessionEnd", "release")];
Expand Down Expand Up @@ -293,7 +293,7 @@ const GROK_HOOK_ASSET: &str = if cfg!(windows) {
} else {
include_str!("assets/grok/herdr-agent-state.sh")
};
const GROK_INTEGRATION_VERSION: u32 = 1;
const GROK_INTEGRATION_VERSION: u32 = 2;

pub(crate) const INSTALL_WARNING_PREFIX: &str = "warning:";

Expand Down
3 changes: 2 additions & 1 deletion src/integration/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2736,6 +2736,7 @@ fn bundled_integration_asset_versions_match_expected_versions() {
MASTRACODE_HOOK_ASSET,
MASTRACODE_INTEGRATION_VERSION,
),
("grok", GROK_HOOK_ASSET, GROK_INTEGRATION_VERSION),
] {
assert_eq!(
parse_integration_version(asset),
Expand Down Expand Up @@ -2850,7 +2851,7 @@ fn bundled_integration_assets_report_session_refs() {
assert!(!CURSOR_HOOK_ASSET.contains("\"state\":"));
assert!(!CURSOR_HOOK_ASSET.contains("pane.release_agent"));
assert!(MASTRACODE_HOOK_ASSET.contains("HERDR_INTEGRATION_ID=mastracode"));
assert!(MASTRACODE_HOOK_ASSET.contains("HERDR_INTEGRATION_VERSION=2"));
assert!(MASTRACODE_HOOK_ASSET.contains("HERDR_INTEGRATION_VERSION=3"));
assert!(MASTRACODE_HOOK_ASSET.contains("session_id"));
assert!(!MASTRACODE_HOOK_ASSET.contains("run_id"));
assert!(MASTRACODE_HOOK_ASSET.contains("agent_session_id"));
Expand Down
95 changes: 95 additions & 0 deletions tests/cli/hooks.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use super::harness::*;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

fn run_claude_hook(action: &str, hook_input: &str) -> Option<serde_json::Value> {
run_shell_hook(
Expand Down Expand Up @@ -109,6 +111,99 @@ fn run_shell_hook_with_env(
request.map(|line| serde_json::from_str(&line).unwrap())
}

#[cfg(unix)]
#[test]
fn shell_hooks_ignore_unusable_python_on_path() {
let base = unique_test_dir();
fs::create_dir_all(&base).unwrap();
let python_path = base.join("python3");
fs::write(
&python_path,
r#"#!/bin/sh
printf invoked >"$PYTHON_INVOKED"
printf 'error: tool python3 not found on stdout\n'
printf 'error: tool python3 not found on stderr\n' >&2
exit 1
"#,
)
.unwrap();
let mut permissions = fs::metadata(&python_path).unwrap().permissions();
permissions.set_mode(0o755);
fs::set_permissions(&python_path, permissions).unwrap();

let hooks: &[(&str, &[&str])] = &[
(
"src/integration/assets/claude/herdr-agent-state.sh",
&["session"],
),
(
"src/integration/assets/codex/herdr-agent-state.sh",
&["session"],
),
("src/integration/assets/copilot/herdr-agent-state.sh", &[]),
(
"src/integration/assets/devin/herdr-agent-state.sh",
&["session"],
),
(
"src/integration/assets/droid/herdr-agent-state.sh",
&["session"],
),
(
"src/integration/assets/grok/herdr-agent-state.sh",
&["session"],
),
(
"src/integration/assets/mastracode/herdr-agent-state.sh",
&["session"],
),
];

let invoked_path = base.join("python-invoked");
for (asset_path, args) in hooks {
let _ = fs::remove_file(&invoked_path);
let hook_path = Path::new(env!("CARGO_MANIFEST_DIR")).join(asset_path);
let mut child = Command::new("sh")
.arg(hook_path)
.args(*args)
.env("PATH", format!("{}:/usr/bin:/bin", base.display()))
.env("PYTHON_INVOKED", &invoked_path)
.env("HERDR_ENV", "1")
.env("HERDR_SOCKET_PATH", base.join("missing.sock"))
.env("HERDR_PANE_ID", "p_test")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(b"{}").unwrap();
let output = child.wait_with_output().unwrap();

assert!(
invoked_path.exists(),
"{asset_path} did not attempt to invoke python"
);
assert!(
output.status.success(),
"{asset_path} exposed unusable python: status={:?} stderr={}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
assert!(
output.stdout.is_empty(),
"{asset_path} exposed unusable python stdout: {}",
String::from_utf8_lossy(&output.stdout)
);
assert!(
output.stderr.is_empty(),
"{asset_path} exposed unusable python stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}

cleanup_test_base(&base);
}

#[test]
fn claude_hook_ignores_state_actions() {
let subagent_input = r#"{"hook_event_name":"Notification","agent_id":"agent-abc123","agent_type":"Explore","notification_type":"permission_prompt"}"#;
Expand Down
Loading