fix: remove project .claude/settings.json — use global config (#1089)
## Problem The project-level `.claude/settings.json` overrides the global `skipDangerousModePermissionPrompt` flag, causing Claude Code to show an interactive bypass-permissions confirmation dialog in worktrees. This blocks ALL non-interactive agent sessions on evolution. ## Root cause Claude Code resolves settings per-project. When a `.claude/settings.json` exists in the repo, it takes precedence over `~/.claude/settings.json`. The harb repo has one (with supervisor hooks) but without `skipDangerousModePermissionPrompt: true`. Result: every agent tmux session shows a confirmation prompt and dies. ## Fix Delete `.claude/settings.json` and `.claude/hooks/supervisor/` entirely: - The supervisor hooks are legacy from claude-code-supervisor - agent-session.sh injects its own hooks per worktree at runtime - Disinto has no project-level `.claude/` and works fine - Global `~/.claude/settings.json` has the correct flags ## Impact **This unblocks all harb agents on evolution.** Currently zero PRs can be processed. Reviewed-on: https://codeberg.org/johba/harb/pulls/1089
This commit is contained in:
parent
46e928ea97
commit
7fd1963480
6 changed files with 0 additions and 403 deletions
|
|
@ -1,139 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Shared functions for claude-code-supervisor hooks and scripts.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Find config file: project .claude-code-supervisor.yml → ~/.config/ → defaults
|
||||
ccs_find_config() {
|
||||
local cwd="${1:-.}"
|
||||
if [ -f "$cwd/.claude-code-supervisor.yml" ]; then
|
||||
echo "$cwd/.claude-code-supervisor.yml"
|
||||
elif [ -f "${HOME}/.config/claude-code-supervisor/config.yml" ]; then
|
||||
echo "${HOME}/.config/claude-code-supervisor/config.yml"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Read a yaml value (simple key.subkey extraction, no yq dependency).
|
||||
# Falls back to default if not found.
|
||||
ccs_config_get() {
|
||||
local config_file="$1"
|
||||
local key="$2"
|
||||
local default="${3:-}"
|
||||
|
||||
if [ -z "$config_file" ] || [ ! -f "$config_file" ]; then
|
||||
echo "$default"
|
||||
return
|
||||
fi
|
||||
|
||||
# Simple grep-based yaml extraction (handles key: value on one line)
|
||||
local value
|
||||
case "$key" in
|
||||
triage.command) value=$(grep -A0 '^\s*command:' "$config_file" | head -1 | sed 's/.*command:\s*["]*//;s/["]*$//' | xargs) ;;
|
||||
triage.model) value=$(awk '/^triage:/,/^[a-z]/' "$config_file" | grep 'model:' | head -1 | sed 's/.*model:\s*["]*//;s/["]*$//' | xargs) ;;
|
||||
triage.max_tokens) value=$(awk '/^triage:/,/^[a-z]/' "$config_file" | grep 'max_tokens:' | head -1 | sed 's/.*max_tokens:\s*//' | xargs) ;;
|
||||
notify.command) value=$(awk '/^notify:/,/^[a-z]/' "$config_file" | grep 'command:' | head -1 | sed 's/.*command:\s*["]*//;s/["]*$//' | xargs) ;;
|
||||
idle.timeout) value=$(awk '/^idle:/,/^[a-z]/' "$config_file" | grep 'timeout_seconds:' | head -1 | sed 's/.*timeout_seconds:\s*//' | xargs) ;;
|
||||
idle.nudge_message) value=$(awk '/^idle:/,/^[a-z]/' "$config_file" | grep 'nudge_message:' | head -1 | sed 's/.*nudge_message:\s*["]*//;s/["]*$//' | xargs) ;;
|
||||
*) value="" ;;
|
||||
esac
|
||||
|
||||
echo "${value:-$default}"
|
||||
}
|
||||
|
||||
# Send a notification via the configured notify command.
|
||||
ccs_notify() {
|
||||
local config_file="$1"
|
||||
local message="$2"
|
||||
|
||||
local notify_cmd
|
||||
notify_cmd=$(ccs_config_get "$config_file" "notify.command" "openclaw gateway call wake --params")
|
||||
|
||||
# Build JSON payload
|
||||
local payload
|
||||
payload=$(jq -n --arg text "$message" --arg mode "now" '{text: $text, mode: $mode}')
|
||||
|
||||
$notify_cmd "$payload" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Run LLM triage via configured command.
|
||||
# Accepts prompt on stdin, returns verdict on stdout.
|
||||
ccs_triage() {
|
||||
local config_file="$1"
|
||||
local prompt="$2"
|
||||
|
||||
local triage_cmd model max_tokens
|
||||
triage_cmd=$(ccs_config_get "$config_file" "triage.command" "claude -p --no-session-persistence")
|
||||
model=$(ccs_config_get "$config_file" "triage.model" "claude-haiku-4-5-20251001")
|
||||
max_tokens=$(ccs_config_get "$config_file" "triage.max_tokens" "150")
|
||||
|
||||
echo "$prompt" | $triage_cmd --model "$model" --max-tokens "$max_tokens" 2>/dev/null
|
||||
}
|
||||
|
||||
# Generate a notify wrapper script the agent can call on completion.
|
||||
# Usage: ccs_generate_notify_script "$config_file" ["/tmp/supervisor-notify.sh"]
|
||||
ccs_generate_notify_script() {
|
||||
local config_file="$1"
|
||||
local script_path="${2:-/tmp/supervisor-notify.sh}"
|
||||
local notify_cmd
|
||||
notify_cmd=$(ccs_config_get "$config_file" "notify.command" "openclaw gateway call wake --params")
|
||||
|
||||
cat > "$script_path" <<SCRIPT
|
||||
#!/bin/bash
|
||||
# Auto-generated by claude-code-supervisor
|
||||
# Usage: $script_path "your summary here"
|
||||
MESSAGE="\${*:-done}"
|
||||
PAYLOAD=\$(jq -n --arg text "cc-supervisor: DONE | agent-reported | \$MESSAGE" --arg mode "now" '{text: \$text, mode: \$mode}')
|
||||
$notify_cmd "\$PAYLOAD" 2>/dev/null || echo "Notify failed (command: $notify_cmd)" >&2
|
||||
SCRIPT
|
||||
chmod +x "$script_path"
|
||||
}
|
||||
|
||||
# Extract environment_hints from config as newline-separated list.
|
||||
# Usage: hints=$(ccs_environment_hints "$config_file")
|
||||
ccs_environment_hints() {
|
||||
local config_file="$1"
|
||||
if [ -z "$config_file" ] || [ ! -f "$config_file" ]; then
|
||||
return
|
||||
fi
|
||||
awk '/^environment_hints:/,/^[a-z]/' "$config_file" \
|
||||
| grep '^\s*-' \
|
||||
| sed 's/^\s*-\s*["]*//;s/["]*$//'
|
||||
}
|
||||
|
||||
# Parse capture-pane output and return session status.
|
||||
# Returns: WORKING | IDLE | DONE | ERROR
|
||||
# Usage: status=$(ccs_parse_status "$pane_output")
|
||||
ccs_parse_status() {
|
||||
local output="$1"
|
||||
local last_lines
|
||||
last_lines=$(echo "$output" | tail -15)
|
||||
|
||||
# DONE: completed task indicators (cost summary line)
|
||||
if echo "$last_lines" | grep -qE '(Brewed for|Churned for) [0-9]+'; then
|
||||
echo "DONE"
|
||||
return
|
||||
fi
|
||||
|
||||
# WORKING: active tool call or thinking
|
||||
if echo "$last_lines" | grep -qE 'esc to interrupt|Running…|Waiting…'; then
|
||||
echo "WORKING"
|
||||
return
|
||||
fi
|
||||
|
||||
# ERROR: tool failures or API errors
|
||||
if echo "$last_lines" | grep -qiE 'API Error:|Exit code [1-9]|^Error:'; then
|
||||
echo "ERROR"
|
||||
return
|
||||
fi
|
||||
|
||||
# IDLE: prompt back with no activity indicator
|
||||
if echo "$last_lines" | tail -3 | grep -qE '^\$ |^❯ |^% |^[a-z]+@.*\$ '; then
|
||||
echo "IDLE"
|
||||
return
|
||||
fi
|
||||
|
||||
# Default: assume working (mid-output, streaming, etc.)
|
||||
echo "WORKING"
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Claude Code PostToolUseFailure hook — fires when a tool call fails.
|
||||
#
|
||||
# Option D: bash pre-filter for known patterns, LLM triage for the rest.
|
||||
#
|
||||
# - API 500 → always triage (transient, likely needs nudge)
|
||||
# - API 429 → log + wait (rate limit, will resolve)
|
||||
# - Other tool errors → triage (might be important)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SUPERVISOR_DIR="${SCRIPT_DIR%/hooks}"
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
TOOL=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
|
||||
ERROR=$(echo "$INPUT" | jq -r '.error // .tool_error // "unknown"' | head -c 500)
|
||||
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
||||
CWD=$(echo "$INPUT" | jq -r '.cwd // "unknown"')
|
||||
|
||||
# Bash pre-filter
|
||||
if echo "$ERROR" | grep -qi "429\|rate.limit"; then
|
||||
# Rate limited — log it, don't triage. It'll resolve.
|
||||
echo "[$(date -u +%FT%TZ)] RATE_LIMITED | $TOOL | $CWD" >> "${CCS_LOG_FILE:-/tmp/ccs-triage.log}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if echo "$ERROR" | grep -qi "500\|internal.server.error\|api_error"; then
|
||||
# API 500 — high chance agent is stuck on this. Triage.
|
||||
"$SUPERVISOR_DIR/triage.sh" "error:api_500" "$CWD" \
|
||||
"Tool: $TOOL | Error: $ERROR | Session: $SESSION_ID" &
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Other errors — could be anything. Let LLM decide.
|
||||
"$SUPERVISOR_DIR/triage.sh" "error:$TOOL" "$CWD" \
|
||||
"Tool: $TOOL | Error: $ERROR | Session: $SESSION_ID" &
|
||||
|
||||
exit 0
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Claude Code Notification hook — fires when Claude is waiting for input.
|
||||
#
|
||||
# Option D: bash pre-filter by notification type.
|
||||
#
|
||||
# - idle_prompt → triage (agent waiting, might need nudge)
|
||||
# - permission_prompt → always triage (might need approval)
|
||||
# - auth_* → skip (internal, transient)
|
||||
# - other → triage
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SUPERVISOR_DIR="${SCRIPT_DIR%/hooks}"
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
NOTIFY_TYPE=$(echo "$INPUT" | jq -r '.type // "unknown"')
|
||||
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
||||
CWD=$(echo "$INPUT" | jq -r '.cwd // "unknown"')
|
||||
MESSAGE=$(echo "$INPUT" | jq -r '.message // ""' | head -c 300)
|
||||
|
||||
case "$NOTIFY_TYPE" in
|
||||
auth_*)
|
||||
# Auth events are transient, skip
|
||||
exit 0
|
||||
;;
|
||||
permission_prompt)
|
||||
# Agent needs permission — might be able to auto-approve, or escalate
|
||||
"$SUPERVISOR_DIR/triage.sh" "notification:permission" "$CWD" \
|
||||
"Permission requested: $MESSAGE | Session: $SESSION_ID" &
|
||||
;;
|
||||
idle_prompt)
|
||||
# Agent is idle, waiting for human input
|
||||
"$SUPERVISOR_DIR/triage.sh" "notification:idle" "$CWD" \
|
||||
"Agent idle, waiting for input. Message: $MESSAGE | Session: $SESSION_ID" &
|
||||
;;
|
||||
*)
|
||||
"$SUPERVISOR_DIR/triage.sh" "notification:$NOTIFY_TYPE" "$CWD" \
|
||||
"Type: $NOTIFY_TYPE | Message: $MESSAGE | Session: $SESSION_ID" &
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Claude Code Stop hook — fires when the agent finishes responding.
|
||||
#
|
||||
# Option D: bash pre-filter first, LLM triage only for ambiguous cases.
|
||||
#
|
||||
# Known Stop event fields:
|
||||
# session_id, transcript_path, cwd, permission_mode,
|
||||
# hook_event_name ("Stop"), stop_hook_active
|
||||
#
|
||||
# Note: stop_reason is NOT provided in the Stop event JSON.
|
||||
# We detect state by inspecting the tmux pane instead.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SUPERVISOR_DIR="${SCRIPT_DIR%/hooks}"
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
||||
CWD=$(echo "$INPUT" | jq -r '.cwd // "unknown"')
|
||||
|
||||
# Find matching supervised session
|
||||
STATE_FILE="${CCS_STATE_FILE:-${HOME}/.openclaw/workspace/supervisor-state.json}"
|
||||
if [ ! -f "$STATE_FILE" ]; then
|
||||
exit 0 # No supervised sessions
|
||||
fi
|
||||
|
||||
SOCKET=$(jq -r --arg cwd "$CWD" '
|
||||
.sessions | to_entries[] | select(.value.projectDir == $cwd and .value.status == "running") | .value.socket
|
||||
' "$STATE_FILE" 2>/dev/null || echo "")
|
||||
|
||||
TMUX_SESSION=$(jq -r --arg cwd "$CWD" '
|
||||
.sessions | to_entries[] | select(.value.projectDir == $cwd and .value.status == "running") | .value.tmuxSession
|
||||
' "$STATE_FILE" 2>/dev/null || echo "")
|
||||
|
||||
# No matching supervised session for this project
|
||||
if [ -z "$SOCKET" ] || [ -z "$TMUX_SESSION" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Can we reach the tmux session?
|
||||
if [ ! -S "$SOCKET" ] || ! tmux -S "$SOCKET" has-session -t "$TMUX_SESSION" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PANE_OUTPUT=$(tmux -S "$SOCKET" capture-pane -p -J -t "$TMUX_SESSION" -S -30 2>/dev/null || echo "")
|
||||
|
||||
# Bash pre-filter: check the last few lines for patterns
|
||||
|
||||
# Check for API errors (transient — always triage)
|
||||
if echo "$PANE_OUTPUT" | tail -10 | grep -qiE "API Error: 5[0-9][0-9]|internal.server.error|api_error"; then
|
||||
"$SUPERVISOR_DIR/triage.sh" "stopped:api_error" "$CWD" "$PANE_OUTPUT" &
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for rate limiting
|
||||
if echo "$PANE_OUTPUT" | tail -10 | grep -qiE "429|rate.limit"; then
|
||||
echo "[$(date -u +%FT%TZ)] RATE_LIMITED | stop | $CWD" >> "${CCS_LOG_FILE:-/tmp/ccs-triage.log}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if shell prompt is back (Claude Code exited)
|
||||
if echo "$PANE_OUTPUT" | tail -5 | grep -qE '^\$ |^❯ [^/]|^% |^[a-z]+@.*\$ '; then
|
||||
# Prompt returned — agent might be done or crashed. Triage it.
|
||||
"$SUPERVISOR_DIR/triage.sh" "stopped:prompt_back" "$CWD" "$PANE_OUTPUT" &
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# No prompt back, no errors — agent is likely mid-conversation. Skip silently.
|
||||
exit 0
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Triage a Claude Code event using a fast LLM.
|
||||
# Called by hook scripts when bash pre-filtering can't decide.
|
||||
#
|
||||
# Usage: triage.sh <event-type> <cwd> <context>
|
||||
# event-type: stopped | error | notification
|
||||
# cwd: project directory (used to find config + state)
|
||||
# context: the relevant context (tmux output, error message, etc.)
|
||||
#
|
||||
# Reads supervisor-state.json to find the goal for this session.
|
||||
# Returns one of: FINE | NEEDS_NUDGE | STUCK | DONE | ESCALATE
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/lib.sh"
|
||||
|
||||
EVENT_TYPE="${1:-unknown}"
|
||||
CWD="${2:-.}"
|
||||
CONTEXT="${3:-}"
|
||||
|
||||
CONFIG=$(ccs_find_config "$CWD")
|
||||
|
||||
# Try to find the goal from supervisor state
|
||||
STATE_FILE="${CCS_STATE_FILE:-${HOME}/.openclaw/workspace/supervisor-state.json}"
|
||||
GOAL="unknown"
|
||||
if [ -f "$STATE_FILE" ]; then
|
||||
# Match by cwd/projectDir
|
||||
GOAL=$(jq -r --arg cwd "$CWD" '
|
||||
.sessions | to_entries[] | select(.value.projectDir == $cwd) | .value.goal
|
||||
' "$STATE_FILE" 2>/dev/null || echo "unknown")
|
||||
fi
|
||||
|
||||
PROMPT="You are a coding agent supervisor. A Claude Code session just triggered an event.
|
||||
|
||||
Event: ${EVENT_TYPE}
|
||||
Project: ${CWD}
|
||||
Goal: ${GOAL}
|
||||
|
||||
Recent terminal output:
|
||||
${CONTEXT}
|
||||
|
||||
Classify this situation with exactly one word on the first line:
|
||||
FINE - Agent is working normally, no intervention needed
|
||||
NEEDS_NUDGE - Agent hit a transient error or stopped prematurely, should be told to continue
|
||||
STUCK - Agent is looping or not making progress, needs different approach
|
||||
DONE - Agent completed the task successfully
|
||||
ESCALATE - Situation needs human judgment
|
||||
|
||||
Then a one-line explanation."
|
||||
|
||||
VERDICT=$(ccs_triage "$CONFIG" "$PROMPT")
|
||||
|
||||
echo "$VERDICT"
|
||||
|
||||
# Extract the classification (first word of first line)
|
||||
CLASSIFICATION=$(echo "$VERDICT" | head -1 | awk '{print $1}')
|
||||
|
||||
# Only notify if action is needed
|
||||
case "$CLASSIFICATION" in
|
||||
FINE)
|
||||
# Log silently, don't wake anyone
|
||||
echo "[$(date -u +%FT%TZ)] FINE | $EVENT_TYPE | $CWD" >> "${CCS_LOG_FILE:-/tmp/ccs-triage.log}"
|
||||
;;
|
||||
NEEDS_NUDGE|STUCK|DONE|ESCALATE)
|
||||
ccs_notify "$CONFIG" "cc-supervisor: $CLASSIFICATION | $EVENT_TYPE | cwd=$CWD | $VERDICT"
|
||||
;;
|
||||
*)
|
||||
# Couldn't parse — notify to be safe
|
||||
ccs_notify "$CONFIG" "cc-supervisor: UNKNOWN | $EVENT_TYPE | cwd=$CWD | verdict=$VERDICT"
|
||||
;;
|
||||
esac
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
{
|
||||
"hooks": {
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/debian/harb/.claude/hooks/supervisor/on-stop.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUseFailure": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/debian/harb/.claude/hooks/supervisor/on-error.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Notification": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/debian/harb/.claude/hooks/supervisor/on-notify.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue