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