Created
February 13, 2026 06:38
-
-
Save alextangson/7f42bf0a078b4266f098a4ad61732ae3 to your computer and use it in GitHub Desktop.
OpenClaw + Claude Code Hooks 零轮询脚本 — 邪修大法第二弹
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "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 | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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