Deploy Admin
Caution
Deployment mistakes are irreversible. Pushed tags, published releases, triggered workflows — none undo cleanly. deploy-admin classifies every operation by safety level. A subagent cannot ask for confirmation mid-run — AskUserQuestion is stripped from every subagent at runtime — so MODIFY, SERVICE, DELETE, and PRIVILEGE operations come back as an ## APPROVAL REQUIRED envelope instead of running.
Caution
One agent per target. A multi-repo, multi-environment or multi-service deployment is split per target — the agent will not loop one run over staging and prod, or over several repos. Send them as separate tasks, or take the split proposal it returns.
Tip
Run /brewtools:deploy setup first. The skill writes CLAUDE.local.md with your GitHub config, workflow inventory, and server targets. The agent reads that file automatically on every task start.
Quick reference
| Field | Value |
|---|---|
| Agent name | deploy-admin |
| Model | inherit — runs on the session’s model |
| Tools | Read, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch |
| Triggers | ”deploy”, “github actions”, “workflow”, “release”, “ci cd”, “version bump”, “publish” |
| Safety gate | Returns MODIFY / SERVICE / DELETE / PRIVILEGE operations as an APPROVAL REQUIRED envelope instead of running them |
| Scope | One bounded unit per run; one agent per repo / environment / service |
Where its facts come from
The agent carries no baked-in inventory. On every task start it resolves four things by the same three-step chain — read CLAUDE.local.md, else probe, else ask. It never invents any of them.
| Fact | Read | Probe | If still missing |
|---|---|---|---|
| GitHub config | ## GitHub Config (owner, repo, registry, default branch) | gh repo view --json owner,name,defaultBranchRef | A derived value is never self-approved for a MODIFY+ operation — it carries into the envelope’s HOST: field |
| Workflow inventory | ## Workflows: | ls .github/workflows/ + gh workflow list | Stops and returns the list as ## NEEDS-INPUT — never guesses a workflow to trigger |
| Server targets | ## SSH Servers (hosts, users, keys, ports) | — | Stops and returns a ## NEEDS-INPUT block. Never invents a host |
| Secret names | CLAUDE.local.md may record which secret each workflow expects | gh secret list (READ level — needs admin; if it fails, say so and continue without the list) | — |
Caution
Names only. The agent never reads, prints, or logs a secret value.
Approval contract
A subagent cannot ask, confirm, or wait for input mid-run — AskUserQuestion is stripped from every
subagent at runtime, even when it’s listed in the agent’s tools:. deploy-admin therefore never
executes a destructive operation on its own judgement:
- It performs all non-destructive work and gathers full evidence first.
- Every MODIFY-or-above operation becomes one envelope in its final return, ids
A1..AN:
## APPROVAL REQUIRED
### A1
COMMAND: <exact command, one line>
HOST: <local | user@host>
EFFECT: <what changes, irreversibly or remotely>
ROLLBACK: <exact reverse command, or NONE>
EVIDENCE: <why this is the right command — file:line / run URL / probe output>
PRECONDITION: <what must still hold at execution time>
- It stops — nothing in that block runs. Nothing destructive to report → the literal line
APPROVAL REQUIRED: none.
The caller (main session, which does have AskUserQuestion) reviews the envelope and either runs
the command itself or re-spawns deploy-admin with APPROVED: A1 A3 in the prompt. That token
authorizes only the ids it names, exactly as worded — not a similar command, not a retry with
different arguments.
Scope guard
The agent sizes the task before it touches CI.
| Situation | What the agent does |
|---|---|
| Task fits one bounded unit — one deliverable, ~5 files, ~10 steps | Runs it |
| Bigger, or several independent deliverables | Stops before starting, returns a split proposal: 2-N bounded subtasks, each with scope and suggested owner |
| Several repos / environments / services in one request | Split per target — one agent per repo, per environment, per service. Never one agent looping over all of them |
| Scope grows mid-flight | Stops at the next clean boundary, reports done / remaining / how to split |
| Brief missing GOAL, SCOPE, CONTEXT, CONSUMER or acceptance | States the assumption in the report, or asks once. Never invents scope |
How this changes your request: “release v3.6.0 in staging” and “promote v3.6.0 to prod” are two tasks, not one. Send them separately, or send the broad request and approve the split proposal that comes back.
When to use
- Release a new version — bump semver, generate changelog, commit, tag, push, verify CI green
- Create or update a workflow — write workflow YAML, add matrix, configure caching, set up GHCR push
- Monitor CI — check why a run failed, fetch step logs, rerun failed jobs
- Publish a draft release — review notes, attach assets, flip draft to published
- Deploy to VPS — dispatch workflow, watch run live, SSH health-check the server after deploy
- Manage secrets — list names (never values), set from file, clean up stale entries
Examples
# Release a new version with full pipeline
"Release v3.6.0 with changelog from recent commits"
# Investigate a failed run
"Check why the last docs workflow failed and suggest a fix"
# Trigger a deploy and verify it landed
"Trigger deploy-docs workflow and verify health on the VPS"
Flow
- Read context
Reads
CLAUDE.local.mdfor GitHub config, workflow inventory, server targets, and secret names. Verifiesgh auth status— wrong user stops everything before touching any resource. - Classify operations
Every planned command is assigned a safety level: READ (free), CREATE (free), MODIFY (confirm), SERVICE (confirm), DELETE (always confirm), PRIVILEGE (always confirm). Compound pipelines inherit the highest level of any constituent command.
- Emit approval envelope
MODIFY and above: gathers evidence, then returns an
## APPROVAL REQUIREDenvelope in its final response instead of running the command — it has no way to callAskUserQuestion, that tool is stripped from every subagent. Nothing mutates until the caller re-spawns it withAPPROVED: <ids>. Emergency stop fires immediately if the wrong repo, branch, or secret exposure is detected. - Execute operations
Runs
ghCLI commands in order — workflow dispatch, release create, secret set,git push —tags, Docker build + push to GHCR. Each command result is captured for the final report. - Verify
Resolves the CI run for the exact pushed SHA and watches it with
gh run watch —exit-status— never the newest rows ingh run list, which can show an unrelated run as green. Also checks release state and deployment health via SSH. For plugin releases, also verifies the plugin cache updated correctly. - Report
Outputs a structured table: repository, task, operations executed, highest safety level reached, and per-check verification results. No silent failures.
Internals — safety levels and release pipeline
Safety classification
| Level | Gate | Examples |
|---|---|---|
| READ | free | gh run list/view, gh workflow list, gh release view, gh secret list |
| CREATE | free | Create workflow YAML, gh release create --draft |
| MODIFY | envelope | Edit workflow, gh secret set, git commit, git tag |
| SERVICE | envelope | gh workflow run, gh run rerun, git push origin HEAD, git push origin refs/tags/vX.Y.Z |
| DELETE | always envelope | gh release delete, gh run cancel, remove workflow file |
| PRIVILEGE | always envelope | gh secret delete, branch protection changes, gh workflow disable |
Compound rules:
| Combination | Result |
|---|---|
sudo + any command | PRIVILEGE (overrides base level) |
Pipeline cmd1 | cmd2 | Highest level of both |
curl | bash, wget && chmod +x | PRIVILEGE (arbitrary execution) |
| Multiple operations in one script | Highest level among all operations |
Draft release + undraft (gh release edit --draft=false) | SERVICE (publishes the release) |
Secret names are listed, never their values — the agent will not read, print, or log a secret value.
gh CLI conventions
| Area | Convention |
|---|---|
| Releases | Create with --draft first, publish separately via gh release edit TAG --draft=false (SERVICE level) |
| Secrets | Set from file or stdin: gh secret set NAME < FILE — never --body "VALUE", it lands in shell history |
| Failure triage | gh run view RUN_ID --log-failed before rerunning; gh run watch RUN_ID to follow a live run |
Release pipeline steps
| Step | Command | Level |
|---|---|---|
| 1. Bump version | The project’s own bump script if it ships one; otherwise edit every version file the repo has, all to the SAME X.Y.Z | MODIFY |
| 2. Changelog | git log --oneline vPREV..HEAD → write into the project’s changelog file, in its existing heading style | MODIFY |
| 3-5. Release transaction | Commit, tag, and both pushes staged as ONE envelope, run as a single && chain once approved — never split across turns. git push origin HEAD + git push origin refs/tags/vX.Y.Z, never git push --tags (that publishes every unpushed local tag) | SERVICE |
| 6. Post-release | The project’s own post-release/publish script, if it has one | SERVICE |
| 7. Verify CI | Resolve the run for this commit’s SHA, then gh run watch --exit-status — never the newest rows in gh run list | READ |
| 8. Verify artifact | Whatever this project publishes — container image, npm/PyPI package, a live /version endpoint. Nothing published is not a failure | READ |
Caution
A pushed tag has no rollback. git reset --soft HEAD~1 and git tag -d vX.Y.Z only recover a failure before the first push. Once git push origin refs/tags/vX.Y.Z succeeds, deleting or force-moving that tag is irreversible for anyone who already fetched it — their clone keeps the old object while the tag name now points elsewhere. The non-destructive escape is always the next patch version.
Note
No script path in that table is hardcoded. The agent reads ${CLAUDE_PLUGIN_ROOT}/skills/deploy/references/release-best-practices.md as a worked example from one multi-package repo — the shape of the flow (probe, then run; every version file in lockstep; verify the published artifact), never as commands to execute in yours.
Rule that survives every repo: every version file must end up on the same version. Hand-editing one and missing another is the classic release break, which is why a project bump script is used when one exists.
Docker / GHCR
# Build for linux/amd64 and push
docker build --platform linux/amd64 -t ghcr.io/OWNER/IMAGE:TAG .
docker push ghcr.io/OWNER/IMAGE:TAGDeployed images pin an exact tag. :latest is convenience tagging only — never what a server pulls.
Deleting a GHCR version (gh api -X DELETE /user/packages/container/IMAGE/versions/VERSION_ID) is
DELETE level — always an APPROVAL REQUIRED envelope with ROLLBACK: NONE.
SSH deploy
# Health check
ssh -o ConnectTimeout=10 USER@HOST 'uptime && df -h && docker ps'
# Pull and restart
ssh USER@HOST 'cd /opt/app && docker compose pull && docker compose up -d'Return Contract
Verdict first, <=30 lines, path:line — never workflow YAML bodies, gh run logs, or changelog text pasted into the reply. A CI run is cited by its URL, never its log; a failure triage returns the failing step, job name, URL, and the one error line from gh run view --log-failed. Full logs, long diffs, and per-file version audits go to .claude/reports/YYYYMMDD-HHMMSS_deploy/, returned as a path.
/brewtools:agent-return-setup enforces this at ~1000 / ~2500 est-tokens when installed; the contract itself ships unconditionally.
Deploy skill
The skill that configures GitHub integration and writes CLAUDE.local.md for this agent to read.
ssh-admin agent
Companion agent for VPS management — SSH connections, server config, and Docker operations on remote hosts.
GitHub source
Agent definition with full safety rules and workflow templates.
Brewtools overview
All brewtools skills and agents in one place.
Updating plugins
/brewtools:plugin-update to check and update the brewcode plugin suite in one command.
See the FAQ for details.