|
| 1 | +#!/usr/bin/env bash |
| 2 | +# ADF Meta-Coordinator health checks |
| 3 | +# Usage: ./scripts/adf-meta-coordinator-health.sh [--dry-run] |
| 4 | +# |
| 5 | +# Checks: |
| 6 | +# 1. reconcile_tick SLOW stalls in adf-orchestrator journal |
| 7 | +# 2. non-success agent exits |
| 8 | +# 3. agents killed for exceeding max_cpu_seconds |
| 9 | +# 4. worktree disk usage |
| 10 | +# 5. adf binary version |
| 11 | +# 6. worktree count |
| 12 | +# 7. Gitea API health |
| 13 | + |
| 14 | +set -euo pipefail |
| 15 | + |
| 16 | +DRY_RUN="${1:-}" |
| 17 | +OWNER="terraphim" |
| 18 | +REPO="terraphim-ai" |
| 19 | +LABEL="adf-health-alert" |
| 20 | +EXPECTED_VERSION="adf 1.20.2" |
| 21 | +WORKTREE_DIR="/data/projects/terraphim/terraphim-ai/.worktrees" |
| 22 | +SINCE="4 hours ago" |
| 23 | +WARN_EXIT=0 |
| 24 | + |
| 25 | +if [[ "$DRY_RUN" == "--dry-run" ]]; then |
| 26 | + echo "INFO: dry-run mode; no issues will be created" |
| 27 | +fi |
| 28 | + |
| 29 | +if [[ -z "${GITEA_TOKEN:-}" ]]; then |
| 30 | + echo "ERROR: GITEA_TOKEN is not set" >&2 |
| 31 | + exit 1 |
| 32 | +fi |
| 33 | + |
| 34 | +log_warn() { echo "WARNING: $*"; } |
| 35 | +log_info() { echo "INFO: $*"; } |
| 36 | + |
| 37 | +timestamp() { date -u +"%Y-%m-%dT%H:%M:%SZ"; } |
| 38 | + |
| 39 | +# Search for an already-open issue with this exact title prefix. |
| 40 | +# Prevents duplicate health-alert spam on every cron invocation. |
| 41 | +search_open_issue() { |
| 42 | + local title_prefix="$1" |
| 43 | + curl -s -H "Authorization: token $GITEA_TOKEN" \ |
| 44 | + "$GITEA_URL/api/v1/repos/$OWNER/$REPO/issues?state=open&label=$LABEL&limit=50" \ |
| 45 | + | jq -r --arg prefix "$title_prefix" '.[] | select(.title | startswith($prefix)) | .number' \ |
| 46 | + | head -n1 |
| 47 | +} |
| 48 | + |
| 49 | +create_issue() { |
| 50 | + local title="$1" |
| 51 | + local body="$2" |
| 52 | + if [[ "$DRY_RUN" == "--dry-run" ]]; then |
| 53 | + log_info "[dry-run] would create issue: $title" |
| 54 | + return |
| 55 | + fi |
| 56 | + |
| 57 | + local existing |
| 58 | + existing=$(search_open_issue "$title" || true) |
| 59 | + if [[ -n "$existing" ]]; then |
| 60 | + log_info "Issue #$existing already open for '$title'; skipping" |
| 61 | + return |
| 62 | + fi |
| 63 | + |
| 64 | + gtr create-issue \ |
| 65 | + --owner "$OWNER" \ |
| 66 | + --repo "$REPO" \ |
| 67 | + --title "$title" \ |
| 68 | + --body "$body" \ |
| 69 | + --labels "$LABEL" |
| 70 | +} |
| 71 | + |
| 72 | +# --- 1. reconcile_tick stalls --- |
| 73 | +stall_count=$(sudo journalctl -u adf-orchestrator --since "$SINCE" 2>/dev/null | grep -c 'reconcile_tick SLOW' || true) |
| 74 | +if [[ "$stall_count" -gt 0 ]]; then |
| 75 | + log_warn "$stall_count tick stalls detected in last 4h" |
| 76 | + create_issue "[ADF] Tick-stall detected: $stall_count in 4h" \ |
| 77 | +"$(timestamp): $stall_count reconcile_tick SLOW events detected. |
| 78 | +
|
| 79 | +Theme-ID: adf-health-alert" |
| 80 | + WARN_EXIT=1 |
| 81 | +else |
| 82 | + log_info "0 tick stalls in last 4h" |
| 83 | +fi |
| 84 | + |
| 85 | +# --- 2. non-success agent exits --- |
| 86 | +failures=$(sudo journalctl -u adf-orchestrator --since "$SINCE" 2>/dev/null \ |
| 87 | + | grep 'exit classified' | grep -v 'success' | grep -v 'empty_success' || true) |
| 88 | +failure_count=$(echo "$failures" | grep -c 'exit_class=' || true) |
| 89 | +if [[ "$failure_count" -gt 3 ]]; then |
| 90 | + log_warn "$failure_count non-success agent exits in last 4h" |
| 91 | + create_issue "[ADF] $failure_count agent failures in 4h" \ |
| 92 | +"$(timestamp): $failure_count non-success agent exits in last 4h: |
| 93 | +
|
| 94 | +$(echo "$failures" | tail -20) |
| 95 | +
|
| 96 | +Theme-ID: adf-health-alert" |
| 97 | + WARN_EXIT=1 |
| 98 | +else |
| 99 | + log_info "$failure_count non-success agent exits in last 4h (threshold 3)" |
| 100 | +fi |
| 101 | + |
| 102 | +# --- 3. max_cpu_seconds timeouts --- |
| 103 | +timeouts=$(sudo journalctl -u adf-orchestrator --since "$SINCE" 2>/dev/null | grep 'AGENT EXCEEDED max_cpu_seconds' || true) |
| 104 | +timeout_count=$(echo "$timeouts" | grep -c 'AGENT EXCEEDED' || true) |
| 105 | +if [[ "$timeout_count" -gt 0 ]]; then |
| 106 | + log_warn "$timeout_count agents exceeded max_cpu_seconds in last 4h" |
| 107 | + create_issue "[ADF] $timeout_count agents exceeded max_cpu_seconds" \ |
| 108 | +"$(timestamp): Agents killed after exceeding their configured max_cpu_seconds: |
| 109 | +
|
| 110 | +$(echo "$timeouts" | tail -10) |
| 111 | +
|
| 112 | +Consider increasing max_cpu_seconds or investigating why these agents are running long. |
| 113 | +
|
| 114 | +Theme-ID: adf-health-alert" |
| 115 | + WARN_EXIT=1 |
| 116 | +else |
| 117 | + log_info "0 max_cpu_seconds timeouts in last 4h" |
| 118 | +fi |
| 119 | + |
| 120 | +# --- 4. worktree disk usage --- |
| 121 | +worktree_size=$(du -sm "$WORKTREE_DIR" 2>/dev/null | cut -f1 || echo 0) |
| 122 | +if [[ "$worktree_size" -gt 10240 ]]; then |
| 123 | + log_warn "worktrees using ${worktree_size}MB (>10GB)" |
| 124 | + create_issue "[ADF] Worktree disk usage: ${worktree_size} MB" \ |
| 125 | +"$(timestamp): Worktree directory exceeds 10GB threshold ($WORKTREE_DIR). |
| 126 | +
|
| 127 | +Theme-ID: adf-health-alert" |
| 128 | + WARN_EXIT=1 |
| 129 | +else |
| 130 | + log_info "worktree disk usage: ${worktree_size}MB" |
| 131 | +fi |
| 132 | + |
| 133 | +# --- 5. adf binary version --- |
| 134 | +adf_version=$(/usr/local/bin/adf --version 2>/dev/null || echo 'unknown') |
| 135 | +if [[ "$adf_version" != "$EXPECTED_VERSION" ]]; then |
| 136 | + log_warn "adf binary version mismatch: $adf_version (expected $EXPECTED_VERSION)" |
| 137 | + create_issue "[ADF] Binary version mismatch: $adf_version" \ |
| 138 | +"$(timestamp): Expected: $EXPECTED_VERSION |
| 139 | +Actual: $adf_version |
| 140 | +
|
| 141 | +Theme-ID: adf-health-alert" |
| 142 | + WARN_EXIT=1 |
| 143 | +else |
| 144 | + log_info "adf binary version: $adf_version" |
| 145 | +fi |
| 146 | + |
| 147 | +# --- 6. worktree count (informational) --- |
| 148 | +# Count directories under .worktrees, not raw `ls` output. |
| 149 | +worktree_count=$(find "$WORKTREE_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l) |
| 150 | +if [[ "$worktree_count" -gt 10 ]]; then |
| 151 | + log_info "$worktree_count worktrees present" |
| 152 | +else |
| 153 | + log_info "$worktree_count worktrees present (threshold 10)" |
| 154 | +fi |
| 155 | + |
| 156 | +# --- 7. Gitea API health --- |
| 157 | +# Use the repo endpoint: the service token has write:repository but not read:user, |
| 158 | +# so /api/v1/user returns 403 even when the API is healthy. |
| 159 | +gitea_status=$(curl -s -o /dev/null -w '%{http_code}' \ |
| 160 | + -H "Authorization: token $GITEA_TOKEN" \ |
| 161 | + "$GITEA_URL/api/v1/repos/$OWNER/$REPO" || echo 000) |
| 162 | +if [[ "$gitea_status" != '200' ]]; then |
| 163 | + log_warn "Gitea API returned HTTP $gitea_status" |
| 164 | + create_issue "[ADF] Gitea API unhealthy: HTTP $gitea_status" \ |
| 165 | +"$(timestamp): Gitea API health check failed (HTTP $gitea_status). |
| 166 | +
|
| 167 | +Theme-ID: adf-health-alert" |
| 168 | + WARN_EXIT=1 |
| 169 | +else |
| 170 | + log_info "Gitea API healthy (HTTP 200)" |
| 171 | +fi |
| 172 | + |
| 173 | +echo "=== Meta-coordinator health check complete ===" |
| 174 | +exit "$WARN_EXIT" |
0 commit comments