Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
93 changes: 93 additions & 0 deletions tests/cli/hooks.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::harness::*;
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 +110,98 @@ fn run_shell_hook_with_env(
request.map(|line| serde_json::from_str(&line).unwrap())
}

#[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