forge-deploy · git:20260622.76c31a9 · 2026-06-22 · sha256 7f99dc2e7e6b66e9

forge-deploy git:20260622.76c31a9A

Immutable. This exact content is served forever at /api/v1/blob/7f99dc2e7e6b66e9.

---
name: forge-deploy
description: Deploy a site on Laravel Forge. Always surface-then-approve (preview-deploy first, then deploy --confirm on explicit approval). Optionally watches until done and writes a deploy-incident on failure. Triggers on "deploy", "trigger deployment", "deploy site", "run deployment", "check deployment status".
---

# Forge Deploy

Trigger a deployment on a Forge site. **Always preview before deploying.** A wrong deploy targets the wrong site — preview eliminates that risk.

---

## Surface-then-approve flow

### Step 1 — Preview (read-only, never mutates)

```bash
php ${CLAUDE_PLUGIN_ROOT}/php/forge.php preview-deploy <server> <site>
```

Resolves `<server>` and `<site>` to their canonical SDK records and prints:

```
--- Deploy preview (no action taken) ---
Server: prod-web-01 (ID: 12345, IP: 1.2.3.4)
Site:   myapp.com (ID: 67890)
```

Exit 0. No network write. The hook does not gate this command.

### Step 2 — Relay canonical target to operator

Show the canonical server name, IP, site name, and IDs. Ask for explicit approval.

> "Deploy to **myapp.com** on **prod-web-01** (1.2.3.4)? Reply 'yes' to confirm or 'no' to cancel."

Never auto-confirm. If the operator says anything other than an unambiguous affirmative, cancel.

### Step 3 — On approval only

```bash
php ${CLAUDE_PLUGIN_ROOT}/php/forge.php deploy <server> <site> --confirm
```

Or with watch (polls until the deployment reaches a terminal state):

```bash
php ${CLAUDE_PLUGIN_ROOT}/php/forge.php deploy <server> <site> --confirm --watch
```

`--watch` polls every 5 seconds (max 10 minutes). It exits 0 on `finished`, exits 2 on `failed`/`failed-build`/`cancelled`.

---

## Deployment history

```bash
php ${CLAUDE_PLUGIN_ROOT}/php/forge.php deploy-history <server> <site>
```

Lists recent deployments with ID, status, and commit message.

## Fetch a specific deployment log

```bash
php ${CLAUDE_PLUGIN_ROOT}/php/forge.php deploy-log <server> <site> <deploy-id>
```

---

## On --watch failure: write a deploy-incident

If `deploy --watch` exits 2 (deployment failed/cancelled), write a `deploy-incident` artifact:

**File**: `compiled/deploy-incident-<site-name>-<YYYY-MM-DD>.md`

**Content template**:
```
---
title: Deploy incident — <site-name> <YYYY-MM-DD>
created: <ISO timestamp>
type: deploy-incident
tags: [deploy, failure, <site-name>]
---

## Incident

- Site: <site-name> (ID: <site-id>)
- Server: <server-name> (ID: <server-id>, IP: <ip>)
- Deployment ID: <deploy-id>
- Status: <failed|failed-build|cancelled>
- Detected: <timestamp>

## Commit

<commit message, if available>

## Log tail (scrubbed)

<last ~50 lines of the deployment log, **scrubbed of any credentials, env vars, or secrets**>

## Resolution

(fill in after incident is resolved)
```

**Secret hygiene (critical):** deployment logs frequently contain env dumps, DB connection strings, and API keys. **Never paste raw log content** — always scrub before writing. Fetch the log with `deploy-log`, review it, then excerpt only the error-relevant lines with any credential values redacted (`[REDACTED]`).

---

## Notes

- `<server>` and `<site>` accept name, hostname, URL hostname, or numeric ID. Ambiguous names are rejected.
- The `--confirm` flag is checked by both the in-PHP gate and the `write-confirm-gate.ts` PreToolUse hook. Omitting it always fails — there is no bypass.
- For reading logs without a deploy action, use `/laravel-forge-hermit:forge-logs`.