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

FieldValue
Agent namedeploy-admin
Modelinherit — runs on the session’s model
ToolsRead, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch
Triggers”deploy”, “github actions”, “workflow”, “release”, “ci cd”, “version bump”, “publish”
Safety gateReturns MODIFY / SERVICE / DELETE / PRIVILEGE operations as an APPROVAL REQUIRED envelope instead of running them
ScopeOne 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.

FactReadProbeIf still missing
GitHub config## GitHub Config (owner, repo, registry, default branch)gh repo view --json owner,name,defaultBranchRefA 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 listStops 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 namesCLAUDE.local.md may record which secret each workflow expectsgh 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:

  1. It performs all non-destructive work and gathers full evidence first.
  2. 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>
  1. 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.

SituationWhat the agent does
Task fits one bounded unit — one deliverable, ~5 files, ~10 stepsRuns it
Bigger, or several independent deliverablesStops before starting, returns a split proposal: 2-N bounded subtasks, each with scope and suggested owner
Several repos / environments / services in one requestSplit per target — one agent per repo, per environment, per service. Never one agent looping over all of them
Scope grows mid-flightStops at the next clean boundary, reports done / remaining / how to split
Brief missing GOAL, SCOPE, CONTEXT, CONSUMER or acceptanceStates 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

  1. Read context

    Reads CLAUDE.local.md for GitHub config, workflow inventory, server targets, and secret names. Verifies gh auth status — wrong user stops everything before touching any resource.

  2. 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.

  3. Emit approval envelope

    MODIFY and above: gathers evidence, then returns an ## APPROVAL REQUIRED envelope in its final response instead of running the command — it has no way to call AskUserQuestion, that tool is stripped from every subagent. Nothing mutates until the caller re-spawns it with APPROVED: <ids>. Emergency stop fires immediately if the wrong repo, branch, or secret exposure is detected.

  4. Execute operations

    Runs gh CLI 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.

  5. Verify

    Resolves the CI run for the exact pushed SHA and watches it with gh run watch —exit-status — never the newest rows in gh 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.

  6. 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

LevelGateExamples
READfreegh run list/view, gh workflow list, gh release view, gh secret list
CREATEfreeCreate workflow YAML, gh release create --draft
MODIFYenvelopeEdit workflow, gh secret set, git commit, git tag
SERVICEenvelopegh workflow run, gh run rerun, git push origin HEAD, git push origin refs/tags/vX.Y.Z
DELETEalways envelopegh release delete, gh run cancel, remove workflow file
PRIVILEGEalways envelopegh secret delete, branch protection changes, gh workflow disable

Compound rules:

CombinationResult
sudo + any commandPRIVILEGE (overrides base level)
Pipeline cmd1 | cmd2Highest level of both
curl | bash, wget && chmod +xPRIVILEGE (arbitrary execution)
Multiple operations in one scriptHighest 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

AreaConvention
ReleasesCreate with --draft first, publish separately via gh release edit TAG --draft=false (SERVICE level)
SecretsSet from file or stdin: gh secret set NAME < FILE — never --body "VALUE", it lands in shell history
Failure triagegh run view RUN_ID --log-failed before rerunning; gh run watch RUN_ID to follow a live run

Release pipeline steps

StepCommandLevel
1. Bump versionThe project’s own bump script if it ships one; otherwise edit every version file the repo has, all to the SAME X.Y.ZMODIFY
2. Changeloggit log --oneline vPREV..HEAD → write into the project’s changelog file, in its existing heading styleMODIFY
3-5. Release transactionCommit, 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-releaseThe project’s own post-release/publish script, if it has oneSERVICE
7. Verify CIResolve the run for this commit’s SHA, then gh run watch --exit-status — never the newest rows in gh run listREAD
8. Verify artifactWhatever this project publishes — container image, npm/PyPI package, a live /version endpoint. Nothing published is not a failureREAD

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:TAG

Deployed 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

Use /brewtools:plugin-update to check and update the brewcode plugin suite in one command. See the FAQ for details.