Receber por email
← Voltar para o catálogo

phase-monitor-deploy

Phase agent that watches the post-merge deployment for a ticket. Reads the merge SHA from phase-monitor-merge.json (the signal file phase-monitor-merge writes after `gh pr merge` confirms via REST), subscribes via `catalyst-events wait-for` to deploy events on that SHA, then delegates a live verification check to the /canary skill (gstack). Emits phase.monitor-deploy.complete.<TICKET> on canary su

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/phase-monitor-deploy/svg)](https://www.skillteca.com.br/skills/phase-monitor-deploy)

Skills relacionadas

Ver todas de DevOps e Infra →

Alerta por categoria

Receba novas skills de DevOps e Infra toda segunda

1 email curto, só com as skills novas de DevOps e Infra. 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

phase-monitor-deploy

Greenfield phase agent shipped in CTL-451 (Initiative 1 Phase 5). Runs after phase-pr has merged the PR. Subscribes to GitHub deployment_status events on the merge commit SHA, then delegates canary verification to /canary (gstack) once the deploy reaches a terminal state.

Optimized for Haiku — the body is purely procedural shell. The model context is only used to gate the /canary skill invocation (which itself decides how aggressively to probe the live URL).

Inputs

Environment:

  • TICKET — Linear identifier (e.g. CTL-451). Required.
  • WORKER_DIR — directory containing phase-monitor-merge.json (read, primary input) and where

phase-monitor-deploy.json (write) lands. Defaults to ${ORCH_DIR}/workers/${TICKET} if set, else $(pwd).

  • PHASE_DEPLOY_TIMEOUT_SEC — seconds to wait for a deployment_status event matching the merge

SHA. Default 1800 (30 minutes). Setting to a small value is the documented way to skip deploy verification in test/dev environments.

  • PHASE_DEPLOY_ENV — GitHub Deployment environment name to match (default production). Set

per-project as needed.

  • PHASE_CANARY_CMD — command line used to invoke the canary skill. Default

claude --model haiku -p /canary --output-format json. Test runners override this with a stub that emits a fixture canary result.

  • CATALYST_ORCHESTRATOR_ID, CATALYST_SESSION_ID — used for event trace/span id derivation.

gh CLI on $PATH, authenticated against the GitHub repo, is required only when phase-monitor-merge.json exists but .pr.mergeCommitSha is empty (the REST fallback path). In the common case (where phase-monitor-merge recorded the SHA successfully), gh is not invoked. The fallback also reads PR number from phase-pr.json.

phase-monitor-merge.json contract (input shape)

{
  "pr": {
    "mergedAt": "2026-05-18T22:00:00Z",
    "ciStatus": "merged",
    "mergeCommitSha": "abc123..."
  }
}

The skill reads .pr.mergeCommitSha only; everything else is informational. The file is written by [[phase-monitor-merge]] after gh pr merge --squash confirms via REST (gh api repos/<owner>/<repo>/pulls/<num> returns .merged == true).

/goal

/goal "The deploy for the merge SHA actually SUCCEEDED — a terminal
       deployment_status success event arrived for the SHA AND the /canary check
       passed — and I have written ${WORKER_DIR}/phase-monitor-deploy.json
       recording that success. If the deploy FAILED (a deployment_status failure
       or a failing canary), I have driven at least one remediation attempt
       rather than passively recording failed/skipped. OR no deployment_status
       event arrived within PHASE_DEPLOY_TIMEOUT_SEC and I have recorded
       status:skipped with a reason (the PR is already merged, so a missing
       deploy event is a skip, not a failure)."

CTL-656: monitor-deploy is not a passive watch — its goal is that the deploy _actually succeeded_, so the /goal evaluator keeps the agent driving toward a green canary, including a remediation attempt on a failed deploy, instead of emitting failed/skipped and walking away on the first terminal signal. The timeout path is the one legitimate early exit. (Production mode only; the CI bash body below remains self-sufficient and deterministic.)

Body

set -uo pipefail

__PM_SCRIPT_PATH="${BASH_SOURCE[0]:-${0}}"
__PM_SKILL_DIR="$(cd "$(dirname "$__PM_SCRIPT_PATH")" && pwd 2>/dev/null || pwd)"
__PM_REPO_ROOT="${PHASE_AGENT_REPO_ROOT:-$(cd "$__PM_SKILL_DIR/../../../.." 2>/dev/null && pwd || pwd)}"
# CTL-512 → CTL-1410 Phase A: every terminal event goes through the production
# wrapper (phase-agent-emit-complete) so the phase signal file's `status` field is
# written canonically in-band. CTL-512 first moved the `skipped` branch here;
# CTL-1410 completes the migration for complete+failed so monitor-deploy →
# teardown advances under executor=sdk — the event-only phase-emit-complete.sh lib
# helper (which only emitted events and never touched the signal file, leaving the
# terminal status to the bg-only reclaim path that silently no-ops under sdk) is no
# longer sourced. The wrapper also owns the broker emit, session DB close, completedAt.
__PM_WRAPPER="${PHASE_EMIT_WRAPPER:-${__PM_REPO_ROOT}/plugins/dev/scripts/phase-agent-emit-complete}"

if [[ ! -x "$__PM_WRAPPER" ]]; then
  echo "phase-monitor-deploy: cannot find phase-agent-emit-complete wrapper at $__PM_WRAPPER" >&2
  exit 1
fi

: "${TICKET:?phase-monitor-deploy: TICKET env var required}"

WORKER_DIR="${WORKER_DIR:-${ORCH_DIR:+${ORCH_DIR}/workers/${TICKET}}}"
WORKER_DIR="${WORKER_DIR:-$(pwd)}"
mkdir -p "$WORKER_DIR"

DEPLOY_TIMEOUT="${PHASE_DEPLOY_TIMEOUT_SEC:-1800}"
DEPLOY_ENV="${PHASE_DEPLOY_ENV:-production}"
CANARY_CMD="${PHASE_CANARY_CMD:-claude --model haiku -p /canary --output-format json}"

# 1. Read merge SHA from phase-monitor-merge.json (the prior phase artifact).
MERGE_FILE="$WORKER_DIR/phase-monitor-merge.json"
if [[ ! -f "$MERGE_FILE" ]]; then
  "$__PM_WRAPPER" --phase monitor-deploy --ticket "$TICKET" --status failed \
    --reason "phase-monitor-merge.json missing at $MERGE_FILE"
  exit 1
fi

MERGE_SHA="$(jq -r '.pr.mergeCommitSha // empty' "$MERGE_FILE" 2>/dev/null)"
if [[ -z "$MERGE_SHA" ]]; then
  # Fall back to gh REST. Mirrors orchestrate-verify.sh:131-156. PR number
  # comes from phase-pr.json (phase-monitor-merge.json does not record it).
  PR_FILE="$WORKER_DIR/phase-pr.json"
  PR_NUMBER=""
  if [[ -f "$PR_FILE" ]]; then
    PR_NUMBER="$(jq -r '.pr.number // empty' "$PR_FILE" 2>/dev/null)"
  fi
  if [[ -z "$PR_NUMBER" ]]; then
    "$__PM_WRAPPER" --phase monitor-deploy --ticket "$TICKET" --status failed \
      --reason "phase-monitor-merge.json has empty .pr.mergeCommitSha and no PR number available for gh REST fallback"
    exit 1
  fi
  REPO="$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "")"
  if [[ -z "$REPO" ]]; then
    "$__PM_WRAPPER" --phase monitor-deploy --ticket "$TICKET" --status failed \
      --reason "phase-monitor-merge.json has empty .pr.mergeCommitSha and gh repo view returned empty"
    exit 1
  fi
  MERGE_SHA="$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.merge_commit_sha // empty' 2>/dev/null || echo "")"
  if [[ -z "$MERGE_SHA" ]]; then
    "$__PM_WRAPPER" --phase monitor-deploy --ticket "$TICKET" --status failed \
      --reason "phase-monitor-merge.json has empty .pr.mergeCommitSha and gh REST fallback also returned empty for pr#${PR_NUMBER}"
    exit 1
  fi
fi

# 2. Subscribe to deployment_status events for this SHA. The filter accepts any
#    deployment_status event whose vcs.revision matches and whose deployment.environment
#    matches PHASE_DEPLOY_ENV. wait-for scans the file from the start (it is not a true
#    live tail) so historical events fire immediately, which is the behavior the test
#    runner depends on.
DEPLOY_FILTER='(.attributes."event.name" | startswith("github.deployment_status"))
               and .attributes."vcs.revision" == "'"$MERGE_SHA"'"
               and .attributes."deployment.environment" == "'"$DEPLOY_ENV"'"'

DEPLOY_EVENT="$(catalyst-events wait-for \
  --filter "$DEPLOY_FILTER" \
  --timeout "$DEPLOY_TIMEOUT" 2>/dev/null || true)"

DEPLOY_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

if [[ -z "$DEPLOY_EVENT" ]]; then
  # 2a. No deploy event observed — emit `skipped` and exit successfully. The
  #     orchestrator treats skipped as success because the PR is already merged
  #     and the deploy pipeline simply did not signal this code path.
  jq -nc \
    --arg ticket "$TICKET" \
    --arg sha "$MERGE_SHA" \
    --arg env "$DEPLOY_ENV" \
    --arg ts "$DEPLOY_TIME" \
    '{
      ticket: $ticket,
      deploy_sha: $sha,
      deploy_env: $env,
      deploy_state: "skipped",
      deploy_time: $ts,
      canary_result: null,
      completed_at: $ts,
      reason: "no deployment_status event matched within ti
Compartilhar X LinkedIn