fix: feat: PostToolUse hook detects phase file writes in real-time (eliminates polling latency) (#278)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ab3efa2402
commit
ac04dc29a6
6 changed files with 132 additions and 6 deletions
|
|
@ -45,10 +45,17 @@ agent_inject_into_session() {
|
|||
|
||||
# Create a tmux session running Claude in the given workdir.
|
||||
# Installs a Stop hook for idle detection (see monitor_phase_loop).
|
||||
# Optionally installs a PostToolUse hook for phase file write detection.
|
||||
# Args: session workdir [phase_file]
|
||||
# Returns 0 if session is ready, 1 otherwise.
|
||||
create_agent_session() {
|
||||
local session="$1"
|
||||
local workdir="${2:-.}"
|
||||
local phase_file="${3:-}"
|
||||
|
||||
# Prepare settings directory for hooks
|
||||
mkdir -p "${workdir}/.claude"
|
||||
local settings="${workdir}/.claude/settings.json"
|
||||
|
||||
# Install Stop hook for idle detection: when Claude finishes a response,
|
||||
# the hook writes a timestamp to a marker file. monitor_phase_loop checks
|
||||
|
|
@ -56,8 +63,6 @@ create_agent_session() {
|
|||
local idle_marker="/tmp/claude-idle-${session}.ts"
|
||||
local hook_script="${FACTORY_ROOT}/lib/hooks/on-idle-stop.sh"
|
||||
if [ -x "$hook_script" ]; then
|
||||
mkdir -p "${workdir}/.claude"
|
||||
local settings="${workdir}/.claude/settings.json"
|
||||
local hook_cmd="${hook_script} ${idle_marker}"
|
||||
if [ -f "$settings" ]; then
|
||||
# Append our Stop hook to existing project settings
|
||||
|
|
@ -79,6 +84,36 @@ create_agent_session() {
|
|||
fi
|
||||
fi
|
||||
|
||||
# Install PostToolUse hook for phase file write detection: when Claude
|
||||
# writes to the phase file via Bash or Write, the hook writes a marker
|
||||
# so monitor_phase_loop can react immediately instead of waiting for
|
||||
# the next mtime-based poll cycle.
|
||||
if [ -n "$phase_file" ]; then
|
||||
local phase_marker="/tmp/phase-changed-${session}.marker"
|
||||
local phase_hook_script="${FACTORY_ROOT}/lib/hooks/on-phase-change.sh"
|
||||
if [ -x "$phase_hook_script" ]; then
|
||||
local phase_hook_cmd="${phase_hook_script} ${phase_file} ${phase_marker}"
|
||||
if [ -f "$settings" ]; then
|
||||
jq --arg cmd "$phase_hook_cmd" '
|
||||
.hooks.PostToolUse = (.hooks.PostToolUse // []) + [{
|
||||
matcher: "Bash|Write",
|
||||
hooks: [{type: "command", command: $cmd}]
|
||||
}]
|
||||
' "$settings" > "${settings}.tmp" && mv "${settings}.tmp" "$settings"
|
||||
else
|
||||
jq -n --arg cmd "$phase_hook_cmd" '{
|
||||
hooks: {
|
||||
PostToolUse: [{
|
||||
matcher: "Bash|Write",
|
||||
hooks: [{type: "command", command: $cmd}]
|
||||
}]
|
||||
}
|
||||
}' > "$settings"
|
||||
fi
|
||||
fi
|
||||
rm -f "$phase_marker"
|
||||
fi
|
||||
|
||||
rm -f "$idle_marker"
|
||||
tmux new-session -d -s "$session" -c "$workdir" \
|
||||
"claude --dangerously-skip-permissions" 2>/dev/null
|
||||
|
|
@ -145,6 +180,15 @@ monitor_phase_loop() {
|
|||
esac
|
||||
fi
|
||||
|
||||
# Check phase-changed marker from PostToolUse hook — if present, the hook
|
||||
# detected a phase file write so we reset last_mtime to force processing
|
||||
# this cycle instead of waiting for the next mtime change.
|
||||
local phase_marker="/tmp/phase-changed-${_session}.marker"
|
||||
if [ -f "$phase_marker" ]; then
|
||||
rm -f "$phase_marker"
|
||||
last_mtime=0
|
||||
fi
|
||||
|
||||
# Check phase file for changes
|
||||
local phase_mtime
|
||||
phase_mtime=$(stat -c %Y "$phase_file" 2>/dev/null || echo 0)
|
||||
|
|
@ -217,6 +261,7 @@ agent_kill_session() {
|
|||
local session="${1:-}"
|
||||
[ -n "$session" ] && tmux kill-session -t "$session" 2>/dev/null || true
|
||||
rm -f "/tmp/claude-idle-${session}.ts"
|
||||
rm -f "/tmp/phase-changed-${session}.marker"
|
||||
}
|
||||
|
||||
# Read the current phase from a phase file, stripped of whitespace.
|
||||
|
|
|
|||
24
lib/hooks/on-phase-change.sh
Executable file
24
lib/hooks/on-phase-change.sh
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash
|
||||
# on-phase-change.sh — PostToolUse hook for phase file write detection.
|
||||
#
|
||||
# Called by Claude Code after every Bash|Write tool execution.
|
||||
# Checks if the tool input references the phase file path and, if so,
|
||||
# writes a "phase-changed" timestamp marker so monitor_phase_loop can
|
||||
# react immediately instead of waiting for the next mtime-based poll.
|
||||
#
|
||||
# Usage (in .claude/settings.json):
|
||||
# {"type": "command", "command": "this-script /path/to/phase-file /path/to/marker"}
|
||||
#
|
||||
# Args: $1 = phase file path, $2 = marker file path
|
||||
|
||||
phase_file="${1:-}"
|
||||
marker_file="${2:-}"
|
||||
|
||||
input=$(cat) # consume hook JSON from stdin
|
||||
|
||||
[ -z "$phase_file" ] || [ -z "$marker_file" ] && exit 0
|
||||
|
||||
# Check if the tool input references the phase file path
|
||||
if printf '%s' "$input" | grep -qF "$phase_file"; then
|
||||
date +%s > "$marker_file"
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue