Guard deployments

Pause a forwarder before a deploy so webhooks queue instead of failing, then resume and confirm recovery with expect and health.

When you deploy the service behind a forwarder, in-flight webhooks hit a target that is restarting. They fail, retry, and may land in the DLQ. Pausing the forwarder first turns that failure window into a queue window: inbound events are held, the deploy proceeds, and a resume drains the backlog into the healthy target. The agent's job is to bracket the deploy and then prove recovery.

forwarder pause and forwarder resume drop into an interactive picker when run without selectors. An agent must always pass both --bucket (-b) and --forwarder (-f) so the command runs non-interactively. Omitting either fails fast with "both --bucket (-b) and --forwarder (-f) flags are required".

The deploy window

sequenceDiagram
    participant A as Agent
    participant R as Repost
    participant T as Target service
    A->>R: forwarder pause -f prod-api
    Note over R,T: inbound webhooks queue, not dropped
    A->>T: deploy new version
    A->>R: forwarder resume -f prod-api
    R->>T: drain queued deliveries
    A->>R: expect / health --fail-on
    R-->>A: recovery confirmed

Pause before deploying

Pausing is a write mutation. Webhooks received while the forwarder is paused are queued, not discarded, and deliver when you resume.

repost forwarder pause \
  --bucket stripe-prod \
  --forwarder prod-api \
  --json
{
  "schema": "repost.forwarder.action/v1",
  "data": {
    "outcome": "PAUSED",
    "results": [
      { "name": "prod-api", "target": "https://api.example.com/webhooks", "type": "EXTERNAL", "success": true }
    ]
  }
}

Confirm every entry in results has success: true before deploying. --forwarder is repeatable. Pass it once per forwarder that points at the service you are deploying, and verify each one paused.

Deploy, then resume

Run your deployment out of band. When the target is healthy again, resume. Queued deliveries begin draining immediately.

repost forwarder resume \
  --bucket stripe-prod \
  --forwarder prod-api \
  --json
{
  "schema": "repost.forwarder.action/v1",
  "data": {
    "outcome": "RESUMED",
    "results": [
      { "name": "prod-api", "target": "https://api.example.com/webhooks", "type": "EXTERNAL", "success": true }
    ]
  }
}

Resuming releases the entire queued backlog at once. If the target is sensitive to bursts, cap delivery with the forwarder's --rate-limit-per-second (set at creation) before the deploy, not after the flood starts.

Confirm recovery

Do not call a deploy healthy because resume returned success: true. That only confirms the forwarder is unpaused. Confirm a real delivery succeeded.

repost expect \
  --bucket stripe-prod \
  --filter 'method:POST AND path:/stripe/*' \
  --timeout 60s

expect exits 0 when a matching event arrives, 7 if none does before the timeout. health --fail-on exits 8 (partial_failure) when the threshold is breached. Treat a clean expect and a clean health as the recovery signal; either non-zero exit means roll forward into diagnosis.

Pause versus disable

forwarder pause: temporary, queued

Stops delivery but keeps queuing inbound webhooks. Reversible with resume, which drains the backlog. This is the deploy-window primitive. Use it whenever you intend to bring the target back.

forwarder disable: durable stop

forwarder disable -b <slug> -f <name> marks the forwarder inactive so it no longer takes deliveries. Use it to retire a target, not to bracket a deploy. Re-enable it from the dashboard or by recreating the route.

If recovery fails

A failed recovery check is recoverable. Nothing was lost.

1
Keep serving

The backlog already drained or is draining. New events keep flowing; you are diagnosing, not firefighting data loss.

2
Find the failures

Run forwards search --failed and inspect forwards chain for the failing events. See Diagnose webhooks.

3
Replay what failed

Replay the DLQ or the failed event IDs behind a dry-run. See Replay deliveries.

For agents

Deploy-guard procedure

  1. Require write scope (auth statusscopes contains write).
  2. forwarder pause -b <slug> -f <name> --json for every target forwarder → assert all data.results[].success == true. Never omit -b/-f (interactive otherwise).
  3. Run the deploy out of band; block on its success.
  4. forwarder resume -b <slug> -f <name> --json → assert all data.results[].success == true.
  5. Verify: expect … --timeout 60s (exit 0 required) AND health --fail-on '…' --json (exit 0 required).
  6. On any non-zero verify exit: forwards search --failedforwards chainreplay the failures.

Continue