Skip to content

Instantly share code, notes, and snippets.

@alextangson
Created February 13, 2026 06:38
Show Gist options
  • Select an option

  • Save alextangson/7f42bf0a078b4266f098a4ad61732ae3 to your computer and use it in GitHub Desktop.

Select an option

Save alextangson/7f42bf0a078b4266f098a4ad61732ae3 to your computer and use it in GitHub Desktop.
OpenClaw + Claude Code Hooks 零轮询脚本 — 邪修大法第二弹
{
"env": {
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
},
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "$HOME/.openclaw/workspace/scripts/on-cc-complete.sh",
"timeout": 30
}
]
}
],
"SessionEnd": [
{
"matcher": "other",
"hooks": [
{
"type": "command",
"command": "$HOME/.openclaw/workspace/scripts/on-cc-complete.sh",
"timeout": 30
}
]
}
]
}
}
#!/bin/bash
# =============================================================
# dispatch-cc.sh — Dispatch a task to Claude Code (background)
# =============================================================
# Called by OpenClaw to launch Claude Code in the background.
# Writes task info to state file, starts Claude Code with
# --print mode, and returns immediately (non-blocking).
#
# Usage:
# dispatch-cc.sh "task description" [work_dir] [--agent-teams]
#
# Examples:
# dispatch-cc.sh "Build a todo app with React" ~/projects/todo
# dispatch-cc.sh "Build a falling sand game" ~/projects/game --agent-teams
# =============================================================
set -euo pipefail
STATE_DIR="/Users/macmini/.openclaw/workspace/state"
TASK_FILE="${STATE_DIR}/cc-task.json"
PID_FILE="${STATE_DIR}/cc-pid"
LOG_FILE="${STATE_DIR}/cc-dispatch.log"
CC_OUTPUT="${STATE_DIR}/cc-output.log"
# Ensure state dir exists
mkdir -p "$STATE_DIR"
# ---- Parse arguments ----
TASK="${1:-}"
WORK_DIR="${2:-$(pwd)}"
USE_AGENT_TEAMS=false
# Check for --agent-teams flag
for arg in "$@"; do
if [ "$arg" = "--agent-teams" ]; then
USE_AGENT_TEAMS=true
fi
done
if [ -z "$TASK" ]; then
echo "ERROR: No task provided"
echo "Usage: dispatch-cc.sh \"task description\" [work_dir] [--agent-teams]"
exit 1
fi
# ---- Logging helper ----
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}
log "=== New dispatch ==="
log "Task: $TASK"
log "Work dir: $WORK_DIR"
log "Agent Teams: $USE_AGENT_TEAMS"
# ---- Clean up previous state ----
rm -f "${STATE_DIR}/cc-result.json" 2>/dev/null
rm -f "${STATE_DIR}/cc-complete.lock" 2>/dev/null
# ---- Write task file ----
DISPATCHED_AT=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
jq -n \
--arg task "$TASK" \
--arg work_dir "$WORK_DIR" \
--arg dispatched_at "$DISPATCHED_AT" \
--argjson agent_teams "$USE_AGENT_TEAMS" \
'{
task: $task,
work_dir: $work_dir,
dispatched_at: $dispatched_at,
agent_teams: $agent_teams
}' > "$TASK_FILE"
log "Task file written to $TASK_FILE"
# ---- Ensure work directory exists ----
mkdir -p "$WORK_DIR"
# ---- Build Claude Code command ----
# Construct the prompt with Agent Teams instruction if requested
CC_PROMPT="$TASK"
if [ "$USE_AGENT_TEAMS" = true ]; then
CC_PROMPT="Use Agent Teams mode with specialized agents for this task. ${TASK}"
fi
# ---- Launch Claude Code in background ----
log "Launching Claude Code in background..."
cd "$WORK_DIR"
nohup claude -p "$CC_PROMPT" \
--print \
--dangerously-skip-permissions \
> "$CC_OUTPUT" 2>&1 &
CC_PID=$!
echo "$CC_PID" > "$PID_FILE"
log "Claude Code launched with PID: $CC_PID"
log "Output logging to: $CC_OUTPUT"
# ---- Output for OpenClaw ----
echo "Claude Code task dispatched successfully!"
echo " Task: $TASK"
echo " Work dir: $WORK_DIR"
echo " Agent Teams: $USE_AGENT_TEAMS"
echo " PID: $CC_PID"
echo " Dispatched at: $DISPATCHED_AT"
echo ""
echo "Claude Code is running in the background."
echo "When complete, a notification will be sent automatically via hooks."
echo "Result will be written to: ${STATE_DIR}/cc-result.json"
exit 0
#!/bin/bash
# =============================================================
# on-cc-complete.sh — Claude Code Stop/SessionEnd Hook Callback
# =============================================================
# Triggered by Claude Code hooks when a task completes (Stop)
# or session ends (SessionEnd). Writes results to a state file
# for OpenClaw to read, and optionally notifies via Feishu.
#
# Deduplication: If cc-result.json already exists and was written
# within the last 60 seconds, skip (prevents Stop + SessionEnd
# from double-firing).
# =============================================================
set -euo pipefail
STATE_DIR="/Users/macmini/.openclaw/workspace/state"
TASK_FILE="${STATE_DIR}/cc-task.json"
RESULT_FILE="${STATE_DIR}/cc-result.json"
LOCK_FILE="${STATE_DIR}/cc-complete.lock"
LOG_FILE="${STATE_DIR}/cc-hook.log"
# Ensure state dir exists
mkdir -p "$STATE_DIR"
# ---- Logging helper ----
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}
log "Hook triggered: $0"
# ---- Read hook input from stdin ----
INPUT=$(cat)
log "Hook input received (${#INPUT} bytes)"
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // "unknown"')
CWD=$(echo "$INPUT" | jq -r '.cwd // "unknown"')
HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "unknown"')
log "Event=$HOOK_EVENT SessionID=$SESSION_ID CWD=$CWD"
# ---- Deduplication: skip if result was written in last 60s ----
if [ -f "$RESULT_FILE" ]; then
RESULT_AGE=$(( $(date +%s) - $(stat -f %m "$RESULT_FILE" 2>/dev/null || echo 0) ))
if [ "$RESULT_AGE" -lt 60 ]; then
log "Dedup: result file is ${RESULT_AGE}s old (< 60s), skipping"
exit 0
fi
fi
# ---- Acquire simple lock (prevent race condition) ----
if [ -f "$LOCK_FILE" ]; then
LOCK_AGE=$(( $(date +%s) - $(stat -f %m "$LOCK_FILE" 2>/dev/null || echo 0) ))
if [ "$LOCK_AGE" -lt 30 ]; then
log "Lock exists and is ${LOCK_AGE}s old, skipping"
exit 0
fi
fi
echo $$ > "$LOCK_FILE"
# ---- Read task info (written by dispatch script) ----
TASK_NAME="unknown"
TASK_DISPATCHED=""
WORK_DIR="$CWD"
if [ -f "$TASK_FILE" ]; then
TASK_NAME=$(jq -r '.task // "unknown"' "$TASK_FILE")
TASK_DISPATCHED=$(jq -r '.dispatched_at // ""' "$TASK_FILE")
WORK_DIR=$(jq -r '.work_dir // "'"$CWD"'"' "$TASK_FILE")
log "Task file found: $TASK_NAME"
else
log "No task file found, using defaults"
fi
# ---- Calculate duration ----
COMPLETED_AT=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
DURATION="unknown"
if [ -n "$TASK_DISPATCHED" ]; then
# Use python3 for reliable cross-platform UTC parsing
START_TS=$(python3 -c "
from datetime import datetime, timezone
try:
dt = datetime.fromisoformat('${TASK_DISPATCHED}'.replace('Z','+00:00'))
print(int(dt.timestamp()))
except:
print(0)
" 2>/dev/null || echo 0)
END_TS=$(date +%s)
if [ "$START_TS" -gt 0 ]; then
ELAPSED=$(( END_TS - START_TS ))
if [ "$ELAPSED" -ge 3600 ]; then
HOURS=$(( ELAPSED / 3600 ))
MINUTES=$(( (ELAPSED % 3600) / 60 ))
SECONDS=$(( ELAPSED % 60 ))
DURATION="${HOURS}h${MINUTES}m${SECONDS}s"
else
MINUTES=$(( ELAPSED / 60 ))
SECONDS=$(( ELAPSED % 60 ))
DURATION="${MINUTES}m${SECONDS}s"
fi
fi
fi
# ---- Collect project file tree (max 30 lines) ----
FILE_TREE=""
if [ -d "$WORK_DIR" ]; then
FILE_TREE=$(find "$WORK_DIR" -maxdepth 3 \
-not -path '*/node_modules/*' \
-not -path '*/.git/*' \
-not -path '*/dist/*' \
-not -path '*/__pycache__/*' \
-not -name '.DS_Store' \
2>/dev/null | head -30 | sed "s|$WORK_DIR/||g" || echo "")
fi
# ---- Write result JSON ----
jq -n \
--arg session_id "$SESSION_ID" \
--arg hook_event "$HOOK_EVENT" \
--arg task "$TASK_NAME" \
--arg work_dir "$WORK_DIR" \
--arg status "completed" \
--arg completed_at "$COMPLETED_AT" \
--arg duration "$DURATION" \
--arg transcript "$TRANSCRIPT_PATH" \
--arg file_tree "$FILE_TREE" \
'{
session_id: $session_id,
hook_event: $hook_event,
task: $task,
work_dir: $work_dir,
status: $status,
completed_at: $completed_at,
duration: $duration,
transcript_path: $transcript,
file_tree: $file_tree
}' > "$RESULT_FILE"
log "Result written to $RESULT_FILE"
# ---- Notify OpenClaw via wake event ----
# Use openclaw CLI to send a message that triggers result reading
OPENCLAW_BIN=$(which openclaw 2>/dev/null || echo "/Users/macmini/.openclaw/bin/openclaw")
if [ -x "$OPENCLAW_BIN" ] || command -v openclaw &>/dev/null; then
# Send wake event to OpenClaw's main session
openclaw send "📋 Claude Code 任务完成通知:任务「${TASK_NAME}」已完成,耗时 ${DURATION},工作目录: ${WORK_DIR}。请读取 ${RESULT_FILE} 查看详细结果。" 2>/dev/null &
log "Wake event sent to OpenClaw"
else
log "WARNING: openclaw CLI not found, skipping wake event"
fi
# ---- Cleanup lock ----
rm -f "$LOCK_FILE"
log "Hook completed successfully"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment