---
name: Ops Deploy
slug: ops-deploy
category: DevOps
description: Ops Deploy gathers ECS, Vercel, and GitHub Actions deployment status into a single dashboard. Use it when you need to check service health, recent deployments, or CI state.
github: "https://github.com/Lifecycle-Innovations-Limited/claude-ops/tree/main/claude-ops/skills/ops-deploy"
language: Shell
stars: 186
forks: 21
install: "npx degit https://github.com/Lifecycle-Innovations-Limited/claude-ops/tree/main/claude-ops/skills/ops-deploy ~/.claude/skills/ops-deploy"
installs_to: ~/.claude/skills/ops-deploy
source_path: claude-ops/skills/ops-deploy/SKILL.md
collection_size: 25
category_size: 1013
collection_url: "https://dirskills.com/collections/Lifecycle-Innovations-Limited/claude-ops"
added: 2026-09-06T05:20:36.611Z
last_synced: 2026-09-06T05:20:36.611Z
canonical_url: "https://dirskills.com/skills/ops-deploy"
---

# Ops Deploy

Ops Deploy gathers ECS, Vercel, and GitHub Actions deployment status into a single dashboard. Use it when you need to check service health, recent deployments, or CI state.

**Install:**

```bash
npx degit https://github.com/Lifecycle-Innovations-Limited/claude-ops/tree/main/claude-ops/skills/ops-deploy ~/.claude/skills/ops-deploy
```

## README

# OPS ► DEPLOY STATUS

Load `ops-rules` before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If `AskUserQuestion` / `Workflow` are missing, follow Rule 10 in `ops-rules` (Hermes: numbered options / two-turn Telegram card; `delegate_task`).

## Runtime Context

Before executing, load available context:

1. **Preferences**: Read `${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json`
   - `timezone` — display all deploy timestamps in the correct timezone

2. **Daemon health**: Read `${CLAUDE_PLUGIN_DATA_DIR}/daemon-health.json`
   - Check `infra-monitor` status — if not running, note that ECS data may be stale

3. **Secrets**: AWS and Vercel credentials required.
   ### Secret Resolution
   - AWS: check `$AWS_PROFILE` / `$AWS_ACCESS_KEY_ID` → `doppler secrets get AWS_ACCESS_KEY_ID --plain` → vault query cmd from prefs
   - Vercel token: check `$VERCEL_TOKEN` → `doppler secrets get VERCEL_TOKEN --plain` → vault

## CLI/API Reference

### aws CLI

| Command                                                                     | Usage               | Output                                                                          |
| --------------------------------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------- |
| `aws ecs list-clusters --output json`                                       | All ECS clusters    | `{clusterArns: [...]}`                                                          |
| `aws ecs list-services --cluster <name> --output json`                      | Services in cluster | `{serviceArns: [...]}`                                                          |
| `aws ecs describe-services --cluster <name> --services <arn> --output json` | Service health      | `{services: [{serviceName, status, runningCount, desiredCount, pendingCount}]}` |
| `aws logs tail /ecs/<service> --since 1h --format short`                    | ECS logs            | Log lines                                                                       |

### gh CLI (GitHub)

| Command                                                                                                   | Usage          | Output                         |
| --------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------ |
| `gh run list --repo <owner/repo> --limit 5 --json status,conclusion,name,headBranch,createdAt,databaseId` | CI runs        | JSON array                     |
| `gh run view <id> --repo <repo> --log-failed`                                                             | Failed CI logs | Log output                     |
| `gh api repos/<repo>/actions/runs/<run-id> --jq .status,.conclusion`                                      | Poll one CI run | JSON fields (REST bucket)     |

`gh run watch` and `gh pr checks --watch` are banned — they poll every 2-5s with no
cap. `hooks/gh-watch-guard.sh` denies them. Poll the REST endpoint above on a `sleep
30` floor with a finite tick count instead.

---

## Agent Teams support

If `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` is set, use **Agent Teams** when checking deploy platforms in parallel. This enables:

- Agents share context and can coordinate mid-flight
- You can steer priorities in real-time
- Agents report progress as they complete

**Team setup** (only when flag is enabled):

```
TeamCreate("deploy-team")
Agent(team_name="deploy-team", name="ecs-checker", prompt="List all ECS clusters and describe service health, running/desired counts")
Agent(team_name="deploy-team", name="vercel-checker", prompt="List Vercel projects and recent deployments with status")
Agent(team_name="deploy-team", name="ci-checker", prompt="Check GitHub Actions runs across all registered repos for failures")
```

If the flag is NOT set, use standard fire-and-forget subagents.

## Phase 1 — Gather deploy data in parallel

### ECS services (all clusters)

```bash
${CLAUDE_PLUGIN_ROOT}/bin/ops-infra 2>/dev/null || \
aws ecs list-clusters --output json 2>/dev/null
```

### ECS service details

```bash
for cluster in $(aws ecs list-clusters --output json 2>/dev/null | jq -r '.clusterArns[]'); do
  cluster_name=$(basename "$cluster")
  aws ecs list-services --cluster "$cluster_name" --output json 2>/dev/null | \
    jq -r '.serviceArns[]' | while read svc; do
    aws ecs describe-services --cluster "$cluster_name" --services "$svc" \
      --output json 2>/dev/null | jq '.services[] | {name: .serviceName, desired: .desiredCount, running: .runningCount, pending: .pendingCount, image: (.taskDefinition // "unknown"), status: .status}'
  done
done
```

### Recent GitHub Actions runs (registry-driven)

```bash
REGISTRY="${CLAUDE_PLUGIN_ROOT}/scripts/registry.json"
[ -f "$REGISTRY" ] || REGISTRY="${CLAUDE_PLUGIN_ROOT}/scripts/registry.example.json"
for repo in $(jq -r '.projects[] | select(.gsd == true) | .repos[]' "$REGISTRY" 2>/dev/null); do
  echo "=== $repo ==="
  gh run list --repo "$repo" --limit 5 --json status,conclusion,name,headBranch,createdAt,databaseId 2>/dev/null
done
```

### Vercel deployments

Use `mcp__claude_ai_Vercel__list_projects` then `mcp__claude_ai_Vercel__list_deployments` for each project (limit 5 per project).

---

## Phase 2 — Render dashboard

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► DEPLOY STATUS — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ECS SERVICES
 CLUSTER    SERVICE         D/R/P   STATUS   LAST DEPLOY
 ─────────────────────────────────────────────────────
 [cluster]  [service]       [x/x/x] ACTIVE   [time ago]
 ...

VERCEL DEPLOYMENTS
 PROJECT     ENV        STATUS    COMMIT    DEPLOYED
 ─────────────────────────────────────────────────────
 [project]   production  READY    [sha]     [time ago]
 ...

CI/CD PIPELINE
 REPO              BRANCH   WORKFLOW        STATUS    AGE
 ─────────────────────────────────────────────────────
 example-api       main     Deploy API      ✓ success  2h
 example-web       dev      Build            ✗ failure  1h
 ...

PENDING DEPLOYS (branch ready, not yet deployed)
 [repo] [branch] [PR#] [CI status] → needs merge to trigger

──────────────────────────────────────────────────────
```

After rendering, use **batched AskUserQuestion calls** (max 4 options each). Only show actions relevant to the current state (e.g., skip "View logs for failing service" if nothing is failing). If <=4 relevant actions, use a single call. If >4, batch:

AskUserQuestion call 1:

```
  [View logs for [failing service]]
  [Trigger manual deploy for [project]]
  [View build logs for [failing CI run]]
  [More actions...]
```

AskUserQuestion call 2 (only if "More actions..."):

```
  [Check Vercel [project] runtime logs]
  [Open GitHub Actions for [repo]]
  [Back to dashboard]
```

---

## Deep-dive by project

If `$ARGUMENTS` has a project alias, show only that project's deploy info + last 10 CI runs + option to view logs.

For failing deploys: offer to view logs via `mcp__claude_ai_Vercel__get_deployment_build_logs` or ECS CloudWatch logs.

**If user selects manual deploy (option b)**, confirm with `AskUserQuestion` before triggering:

```
Trigger deploy for [project]:
  Environment: [production/staging]
  Branch: [branch]
  Last commit: [sha] — [message]

  [Deploy now]  [View diff since last deploy first]  [Cancel]
```

**If user selects to view logs**, show the logs and use `AskUserQuestion`:

```
  [Dispatch fix agent for this failure]  [Redeploy]  [Back to dashboard]
```

---

## Native tool usage

### Monitor — live deploy streaming

When watching a deploy in progress, call the approved monitor script. Do NOT
hand-roll a `gh api` polling loop: `hooks/gh-watch-guard.sh` denies any
for/while/until loop containing a quota-spending `gh` verb, so a hand-rolled
loop is rejected before it ever polls. `gh run watch` is denied for the same
reason (it polls every 2-5s with no cap).

```bash
${CLAUDE_PLUGIN_ROOT}/scripts/ops-deploy-monitor.sh <owner/repo> <pr-number>
```

That script owns the bounded poll: a tick cap, a wall-clock deadline, a 25s
sleep floor and a REST rate gate, all in one place. It is also what the
`bin/ops-deploy-fix-merge-trigger` PostToolUse hook spawns, so the interactive
path and the automatic path share one implementation and one set of limits.

Only a `/rate_limit`-only loop is exempt from the guard (that endpoint is
quota-free) — useful for blocking until quota recovers, not for watching a run.

For ECS deploys: `Monitor(command: "aws ecs wait services-stable --cluster <cluster> --services <service>")`

### Tasks — deploy tracking

Use `TaskCreate` per project being deployed. Update with `TaskUpdate` as deploys succeed/fail.

### WebFetch — Vercel fallback

When Vercel MCP tools are unavailable, use `WebFetch` with the Vercel API directly:

```
WebFetch(url: "https://api.vercel.com/v6/deployments?projectId=<id>&limit=5", headers: {"Authorization": "Bearer $VERCEL_TOKEN"})
```

---

## Ledger Integration

**CLAIM_KEY:** `deploy:<project>:<env>` (e.g. `deploy:my-api:production`)

Keyed on project + environment so concurrent deploys of the same target are blocked
for the full claim TTL (30 minutes).

### Pre-flight skip-check

```bash
CLAIM_KEY="deploy:<project>:<env>"
ledger query --claim-key "$CLAIM_KEY" --since=-PT24H
```

If `in_progress` exists, a concurrent deploy is already running — abort and surface
to the user. If `done` exists within the last hour, confirm before re-deploying.

### Claim + resolve

```bash
# Claim when deploy pipeline starts
ledger write \
  --claim-key "$CLAIM_KEY" \
  --kind "file" \
  --status "in_progress" \
  --title "Deploy: <project> → <env>" \
  --ttl-sec 1800

# Resolve after deploy completes or fails
ledger write \
  --claim-key "$CLAIM_KEY" \
  --kind "file" \
  --status "done" \
  --title "Deploy: <project> → <env>" \
  --context "succeeded|failed: <reason>"
```
