rotate-cli docs

Commands

apply

Rotate, propagate, verify, grace. Split into two phases so auto adapters run unattended and manual-assist adapters don't block CI.

The two phases

Default is --auto-only. Pass --manual-only explicitly to run the interactive phase. The two are mutually exclusive. Auto completes in minutes; manual pauses per rotation and takes as long as you need.

--auto-only (default)

Runs every adapter with mode: "auto". Unattended, safe in CI, safe in agent mode. Adapters with mode: "manual-assist" are deferred. The CLI announces them at the end with a rotate-cli apply --manual-only hint.

--manual-only

Runs adapters with mode: "manual-assist". Each rotation pauses:

  1. CLI prints the dashboard URL and instructions.
  2. You create the new secret in the provider dashboard, paste it.
  3. CLI verifies, propagates to consumers, starts grace.
  4. After grace, CLI prompts for revoke, you delete the old secret in the dashboard.

Requires an interactive TTY. Agent mode refuses this flag.

Full flag list

FlagMeaning
--provider <name>Filter by adapter.
--tag <name>Filter by tag (non-sensitive, sensitive).
--from-scanRead the scan cache instead of rotate.config.yaml.
--auto-onlyDefault. Run only mode: "auto" adapters.
--manual-onlyRun only mode: "manual-assist" adapters.
--confirm-bulkRequired when --from-scan picks >20 rotations.
--yesSkip interactive confirmations.
--reason <string>Justification (required in agent mode).
--max-rotations <n>Hard cap (required in agent mode).
--skip-unknownSkip secrets where ownership couldn't be determined.
--force-rotate-otherRotate even when ownership says "other" (dangerous).
--no-ownership-checkDisable the gate entirely (forbidden in agent mode).
--parallel <n>Consumer propagation concurrency (default 10).
--no-verifySkip verify step (forbidden in agent mode).
--audit-log <path>Append-only JSON audit of every action.

How it works

  1. Ownership gate. Re-runs who logic, skips other unless --force-rotate-other.
  2. Dedup. Groups entries by (adapter, hash(currentValue)). Same value across projects becomes one rotation with all copies updated.
  3. Create. Adapter mints a new secret (auto mode) or prompts the user (manual mode).
  4. Propagate. Each registered consumer writes the new value (Vercel env, GitHub secret, local .env).
  5. Trigger. Fire redeploys or workflows if the consumer supports it.
  6. Verify. Hit the deployment's canonical endpoint with the new value.
  7. Grace. Rotation enters a 1h grace period with the old secret still valid. Use rotate-cli status to watch.
  8. Revoke. rotate-cli revoke <rotation-id> when consumers are synced. Old secret invalidated.

Example: bulk incident response

$ rotate-cli apply --from-scan --tag non-sensitive \
    --yes --confirm-bulk \
    --reason "vercel-apr-2026 breach"

Fetching siblings... ✓ 14.3s
Grouping 76 entries... 58 unique, 18 deferred to manual phase

[1/58] ✓ clerk-elements                 rotated · 2.1s
[2/58] ✓ vercel-ai-gateway-latex0        rotated · 0.8s
...
[58/58] ✓ resend-railly                  rotated · 1.4s

✓ 58 rotations in grace (1h)
⚠ 18 rotation(s) deferred to the other phase:
  firecrawl (10), trigger-dev (5), uploadthing (3)
  → rotate-cli apply --from-scan --manual-only

JSON envelope

{
  "command": "apply",
  "status": "success",
  "data": {
    "mode": "auto-only",
    "rotations": [{ "rotation_id": "rot_123", "status": "in_grace", ... }],
    "deferred": [{ "secret_id": "firecrawl-visionboard", "adapter": "firecrawl" }],
    "ownership_summary": { "self": 58, "other": 0, ... }
  },
  "next_actions": [
    "Run `rotate status rot_123 --json` to check sync; `rotate revoke rot_123` when ready",
    "18 rotation(s) deferred to the other phase: firecrawl (10), ..., run `rotate-cli apply --manual-only`"
  ]
}