main
1#!/bin/sh
2# installed by herdr
3# managed by herdr; reinstalling or updating the integration overwrites this file.
4# add custom hooks beside this file instead of editing it.
5# HERDR_INTEGRATION_ID=claude
6# HERDR_INTEGRATION_VERSION=7
7
8set -eu
9
10action="${1:-}"
11hook_input_file="$(mktemp "${TMPDIR:-/tmp}/herdr-claude-hook.XXXXXX")" || exit 0
12trap 'rm -f "$hook_input_file"' EXIT HUP INT TERM
13cat >"$hook_input_file" 2>/dev/null || true
14
15case "$action" in
16 session) ;;
17 *) exit 0 ;;
18esac
19
20[ "${HERDR_ENV:-}" = "1" ] || exit 0
21[ -n "${HERDR_SOCKET_PATH:-}" ] || exit 0
22[ -n "${HERDR_PANE_ID:-}" ] || exit 0
23command -v python3 >/dev/null 2>&1 || exit 0
24
25HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY'
26import json
27import os
28import random
29import socket
30import time
31
32source = "herdr:claude"
33action = os.environ.get("HERDR_ACTION", "")
34pane_id = os.environ.get("HERDR_PANE_ID")
35socket_path = os.environ.get("HERDR_SOCKET_PATH")
36hook_input_file = os.environ.get("HERDR_HOOK_INPUT_FILE")
37
38if not pane_id or not socket_path:
39 raise SystemExit(0)
40
41hook_input = {}
42if hook_input_file:
43 try:
44 with open(hook_input_file, encoding="utf-8") as handle:
45 content = handle.read()
46 if content.strip():
47 hook_input = json.loads(content)
48 except Exception:
49 hook_input = {}
50
51hook_event_name = str(hook_input.get("hook_event_name") or "")
52is_subagent = bool(hook_input.get("agent_id"))
53if is_subagent:
54 raise SystemExit(0)
55if hook_event_name == "SubagentStop":
56 # SubagentStop is a completion event. Older Herdr integrations mapped it
57 # to durable working, but Claude recap/away-summary can emit it after the
58 # main turn has already stopped. Never let it revive an idle pane.
59 raise SystemExit(0)
60request_id = f"{source}:{int(time.time() * 1000)}:{random.randrange(1_000_000):06d}"
61report_seq = time.time_ns()
62session_id = hook_input.get("session_id")
63agent_session_id = session_id if isinstance(session_id, str) and session_id else None
64transcript_path = hook_input.get("transcript_path")
65agent_session_path = transcript_path if isinstance(transcript_path, str) and transcript_path else None
66session_start_source = hook_input.get("source") if hook_event_name == "SessionStart" else None
67if not isinstance(session_start_source, str) or not session_start_source:
68 session_start_source = None
69if agent_session_id:
70 params = {
71 "pane_id": pane_id,
72 "source": source,
73 "agent": "claude",
74 "seq": report_seq,
75 "agent_session_id": agent_session_id,
76 }
77 if agent_session_path:
78 params["agent_session_path"] = agent_session_path
79 if session_start_source:
80 params["session_start_source"] = session_start_source
81 request = {
82 "id": request_id,
83 "method": "pane.report_agent_session",
84 "params": params,
85 }
86else:
87 raise SystemExit(0)
88
89try:
90 client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
91 client.settimeout(0.5)
92 client.connect(socket_path)
93 client.sendall((json.dumps(request) + "\n").encode())
94 try:
95 client.recv(4096)
96 except Exception:
97 pass
98 client.close()
99except Exception:
100 pass
101PY