Receber por email
← Voltar para o catálogo

wait-for-github

Token-efficient Claude Code workspace with parallel agents and persistent memory. Research → Plan → Implement → Validate workflow.

18estrelas
Atualizado há 3 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add coalesce-labs/catalyst

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/wait-for-github/svg)](https://www.skillteca.com.br/skills/wait-for-github)

Skills relacionadas

Ver todas de Pesquisa e Web →

Alerta por categoria

Receba novas skills de Pesquisa e Web toda segunda

1 email curto, só com as skills novas de Pesquisa e Web. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

wait-for-github — Safe GitHub event wait

What this is for

GitHub's GraphQL API costs points per call. gh pr view --json and gh pr checks both use GraphQL. A single worker polling every 30 s burns 120 calls/hr; three concurrent workers drain the 5,000 point/hr budget in under 15 minutes.

catalyst-events wait-for with a long timeout silently never fires when a filter is wrong. Observed in production: a worker sat idle for hours because .attributes."vcs.pr.number" was null on GitHub webhook events (root cause tracked in CTL-234). The 3-minute diagnostic checkpoint in this skill catches that class of failure quickly.

Do not invoke this as a slash command — it is a reference document for skill authors.

Pre-flight: verify event infrastructure

Before starting any wait, confirm the event infrastructure is running:

REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')

STATUS=$(catalyst-monitor status --json 2>/dev/null)
TUNNEL=$(echo "$STATUS" | jq -r '.webhookTunnel.connected // false' 2>/dev/null)
if [ $? -ne 0 ] || [ "$TUNNEL" != "true" ]; then
  echo "WARN: catalyst-monitor not running or tunnel not connected — using REST fallback directly"
  USE_REST=true
fi

If catalyst-monitor is not running or the tunnel is not connected, skip to the REST fallback. Do not attempt event-driven waits against a dead daemon.

Preferred path: broker auto-detect

When the broker daemon is running, register via broker_claim_pr so the broker auto-derives a pr_lifecycle interest and wakes this worker via filter.wake.${CATALYST_SESSION_ID}. This single semantic wake covers all concerns (CI, reviews, comms, BEHIND) with routing-layer dedup and watchdog support. When the daemon is absent, fall back to the two-phase raw event wait.

broker_claim_pr handles the health check internally via wait_for_broker_ready, which retries up to 3 times (CTL-429) before declaring the daemon absent.

Prerequisites

These must be defined before calling broker_claim_pr:

  • $CATALYST_SESSION_ID — set by catalyst-session.sh start at oneshot startup
  • $STATE_SCRIPT — path to catalyst-state.sh (set at oneshot startup)
  • broker_claim_pr, wait_for_broker_ready, broker_daemon_running functions from oneshot/SKILL.md

Snippet

USE_FILTER_DAEMON=false
PR_BASE_BRANCH=$(gh pr view "$PR_NUMBER" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "main")
if broker_claim_pr "$PR_NUMBER" "$TICKET_ID" "$(git branch --show-current)" "$REPO" "$PR_BASE_BRANCH"; then
  USE_FILTER_DAEMON=true
  echo "[Phase 5] Broker registered pr_lifecycle for session ${CATALYST_SESSION_ID} on PR #${PR_NUMBER}"
else
  # Broker absent — emit telemetry and use two-phase fallback
  "$STATE_SCRIPT" event "$(jq -nc \
    --arg sid "${CATALYST_SESSION_ID:-}" --arg pr "${PR_NUMBER:-}" \
    '{ts: (now | todate), event: "broker.fallback.taken",
      detail: {reason: "daemon absent", session_id: $sid, pr: ($pr | tonumber? // $pr)}}')" \
    2>/dev/null || true
  echo "[Phase 5] Broker unavailable — using two-phase wait-for fallback for PR #${PR_NUMBER}"
fi

PR_DONE=false
while [ "$PR_DONE" = "false" ]; do
  if [ "$USE_FILTER_DAEMON" = "true" ] && [ "$USE_REST" != "true" ]; then
    # Broker path: single semantic wake covers all concerns (CI, reviews, comms, BEHIND)
    EVENT=$(catalyst-events wait-for \
      --filter ".attributes.\"event.name\" == \"filter.wake.${CATALYST_SESSION_ID}\"" \
      --timeout 600 2>/dev/null || true)
    WAKE_REASON=$(echo "$EVENT" | jq -r '.body.payload.reason // "unknown"' 2>/dev/null || echo "unknown")
    echo "wake: filter.wake #${PR_NUMBER} — ${WAKE_REASON}"
  else
    # Two-phase fallback — see "Manual fallback" section below
    EVENT=$(catalyst-events wait-for \
      --filter "(.attributes.\"vcs.pr.number\" == ${PR_NUMBER} or (.body.payload.prNumbers // [] | contains([${PR_NUMBER}]))) and (
        .attributes.\"event.name\" == \"github.pr.merged\" or
        .attributes.\"event.name\" == \"github.check_suite.completed\" or
        (.attributes.\"event.name\" | startswith(\"github.pr_review\")) or
        .attributes.\"event.name\" == \"github.push\"
      )" \
      --timeout 180 2>/dev/null || true)
    # ... run diagnostic checkpoint if empty (see "Manual fallback" section) ...
  fi

  # Authoritative REST check (never gh pr view --json)
  PR_JSON=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" 2>/dev/null || echo '{}')
  PR_MERGED=$(echo "$PR_JSON" | jq -r '.merged // false')
  MERGE_STATE=$(echo "$PR_JSON" | jq -r '.mergeable_state // "unknown"')

  [ "$PR_MERGED" = "true" ] && PR_DONE=true
  # ... handle merge states (clean/blocked/behind/dirty/unknown) per oneshot Phase 5 ...

  [ "$USE_REST" = "true" ] && sleep 300
done

Manual fallback: raw two-phase wait (no broker)

Use this only when the broker daemon is absent (detected by broker_claim_pr returning non-zero above) or when you are not running inside an oneshot worker that has broker_claim_pr defined. This pattern wakes on raw GitHub webhook events and includes a diagnostic checkpoint.

Phase 1 — Short wait with diagnostic checkpoint (3 minutes)

PR_NUMBER=342   # set by caller
EVENT=""
_WFG_MATCHED=false
_WFG_USE_PHASE2=false
USE_REST=${USE_REST:-false}

if [ "$USE_REST" != "true" ]; then
  EVENT=$(catalyst-events wait-for \
    --filter ".attributes.\"event.name\" == \"github.pr.merged\" and .attributes.\"vcs.pr.number\" == ${PR_NUMBER}" \
    --timeout 180 2>/dev/null || true)

  if [ -n "$EVENT" ]; then
    _WFG_MATCHED=true
  else
    # Phase 1 timed out — run 3 diagnostics before deciding next step
    echo "Phase 1 timed out after 3 min — running diagnostics..."
    STALLED=false
    FILTER_MISMATCH=false

    # Diagnostic 1: heartbeat check
    _LOG_FILE=~/catalyst/events/$(date -u +%Y-%m).jsonl
    _LOG_LINES=$(wc -l < "$_LOG_FILE" 2>/dev/null | tr -d ' ')
    _SINCE_LINE=$(( ${_LOG_LINES:-0} > 500 ? ${_LOG_LINES:-0} - 500 : 0 ))
    HEARTBEATS=$(catalyst-events tail --since-line "$_SINCE_LINE" 2>/dev/null \
      | jq -c 'select(.attributes."event.name" == "session.heartbeat")' | wc -l | tr -d ' ')
    if [ "${HEARTBEATS:-0}" -eq 0 ]; then
      echo "WARN: No heartbeats in the last 5 min — event log may be stalled"
      STALLED=true
    fi

    # Diagnostic 2: raw event search without the filter
    # Look for the PR by multiple fields to detect filter mismatches
    RAW_HIT=$(catalyst-events tail --since-line "$_SINCE_LINE" 2>/dev/null | jq -c \
      --argjson pr "$PR_NUMBER" \
      'select(
        (.attributes."vcs.pr.number" == $pr) or
        (.body.payload.prNumbers // [] | contains([$pr]))
      )' | head -1)

    if [ -n "$RAW_HIT" ]; then
      echo "WARN: Event arrived but the filter did not match. Raw event:"
      echo "$RAW_HIT" | jq .
      echo "This is a filter mismatch — falling back to REST."
      echo "Consider filing a CTL ticket with the raw event above."
      FILTER_MISMATCH=true
    fi

    # Diagnostic 3: tunnel state re-check
    TUNNEL_STATE=$(catalyst-monitor status --json 2>/dev/null \
      | jq -r '.webhookTunnel.connected // false')
    echo "Tunnel connected: $TUNNEL_STATE"
    if [ "$TUNNEL_STATE" != "true" ]; then
      echo "WARN: Webhook tunnel is not running"
      STALLED=true
    fi

    # Decision
    if [ "$FILTER_MISMATCH" = "true" ] || [ "$STALLED" = "true" ]; then
      echo "Infrastructure issue detected — falling back to REST polling"
      USE_REST=true
    else
      echo "Infrastructure looks healthy — extending to Phase 2 (2-hour wait)"
      _WFG_USE_PHASE2=true
    fi
  fi
fi

Phase 2 — Extended wait (only after diagnostics confirm healthy)

if [ "$_WFG_USE_PHASE2" = "true" ]; then
  EVENT=$(catalyst-events wait-for \
    --filter ".attributes.\"event.name\" == \"github.pr.merged\" and .attributes.\"vcs.pr.number\" == ${PR_NUMBER}" \
    --timeout 7200 2>/dev/null || true)

  [ -n "$EVENT" ] && _WFG_MATCHED=true
fi

REST fallback — au

Compartilhar X LinkedIn