# Building A CI Human Approval Gate

> A practical workflow for using PingRoom agents, approvals, and Questions to gate CI/CD from a phone.

- Published: 2026-06-27
- Author: Mahdi Salmanzade
- Category: Agents
- Canonical: https://pingroom.io/blog/ci-human-approval-gate
- Cover image: https://pingroom.io/blog/ci-human-approval-gate.webp

The clearest technical use case for PingRoom agents is a CI/CD approval gate.

A pipeline finishes tests. It wants to deploy. Before shipping to production, it asks a human. The human receives a PingRoom push and taps Approve or Deny. The pipeline continues or stops.

That is the whole loop. No dashboard. No Slack thread. No email. No laptop required.

## Version 1: Use Agent Approvals

With the current approval primitive, the CI job can create an approval:

```http
POST /api/agent/rooms/{inviteCode}/approvals
Authorization: Bearer AGENT_TOKEN
Content-Type: application/json
```

```json
{
  "title": "Deploy production?",
  "question": "Ship commit abc123 to production?",
  "options": ["approve", "deny"],
  "ttl": 900,
  "correlation_id": "github-run-998812",
  "data": {
    "repo": "mindzone/pingroom",
    "branch": "main",
    "commit": "abc123",
    "run_id": "998812",
    "environment": "production"
  }
}
```

Then it waits:

```http
GET /api/agent/approvals/{approvalId}/wait
Authorization: Bearer AGENT_TOKEN
```

If approved, deploy. If denied or expired, stop.

![](/blog/ci-human-approval-gate-1.webp)

## Shell Shape

A CLI can make this much nicer:

```sh
pingroom ask \
  --room "$PINGROOM_ROOM" \
  --title "Deploy production?" \
  --option approve:Approve \
  --option deny:Deny \
  --ttl 15m \
  --correlation-id "$GITHUB_RUN_ID" \
  --wait
```

The command should use exit codes:

```text
0  answered with approve
10 answered with deny or another non-approve option
20 expired
30 cancelled
```

That makes CI logic simple:

```sh
if pingroom ask --wait ...; then
  ./deploy-production.sh
else
  echo "Deploy blocked"
  exit 1
fi
```

## GitHub Actions Shape

A GitHub Action could wrap the same behavior:

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test
      - uses: pingroom/approval-action@v1
        with:
          room: ${{ secrets.PINGROOM_ROOM }}
          title: Deploy production?
          question: Ship ${{ github.sha }} to production?
          timeout: 15m
        env:
          PINGROOM_AGENT_TOKEN: ${{ secrets.PINGROOM_AGENT_TOKEN }}
      - run: ./deploy-production.sh
```

The action should print the approval id and correlation id into logs for audit, but never print the token.

## Version 2: General Questions

The Question primitive expands the gate beyond approve or deny.

A pipeline could ask:

```json
{
  "prompt": "Where should I deploy commit abc123?",
  "options": [
    { "value": "production", "label": "Production" },
    { "value": "staging", "label": "Staging" },
    { "value": "cancel", "label": "Cancel" }
  ],
  "responder_scope": "direct",
  "target_user_id": "user_123",
  "ttl": 900,
  "correlation_id": "github-run-998812"
}
```

The CI job can branch based on `answer.value`.

## Safety Rules

Use short TTLs. Always include the commit, branch, environment, and CI run id in `data`. Keep secrets out of the payload. Treat expiry as a stop, not a success. Verify outgoing webhooks if using event-driven continuation. Scope the agent token narrowly.

Most importantly, do not ask for approval after the dangerous action. The Ping should gate the deploy, not announce that it already happened.

## Why This Is A Strong Wedge

This workflow explains PingRoom's agent vision in one minute.

An agent or CI system reaches a human through push. The human makes a bounded decision. The workflow continues with a structured answer. That is the notification fabric in its most practical form.
