post-deploy
Use when running post-deploy file-match actions after a merge (stage 3 of the post-merge pipeline).
How do I install this agent skill?
npx skills add https://github.com/fellowship-dev/dogfooded-skills --skill post-deployIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill automates post-deployment actions by matching changed files in a GitHub PR to specific tasks defined in the repository configuration. While it is a legitimate development tool, it executes scripts directly from the repository and processes untrusted PR metadata like titles and file paths without sanitization, creating a surface for command interference and indirect prompt injection.
- Socketwarn
1 alert: gptAnomaly
- Snykwarn
Risk: MEDIUM · 1 issue
What does this agent skill do?
post-deploy
Stage 3 of the autonomous deployment pipeline. Triggered by the deployed label. Reads the PR's changed files, matches them against configured patterns, and runs targeted actions: ECR image rebuilds, config reload verification, smoke tests, or custom scripts.
When to Use
- Triggered by
deployedlabel via event-rules (after deployment-checker confirms live deploy) - Manual:
/post-deploy PR_NUMBER org/repo
Invocation
/post-deploy 42 fellowship-dev/pylot
Runbook
Step 0: Dedup Gate
PR=$1
REPO=$2
# Check if post-deploy already ran (look for a post-deploy comment)
EXISTING=$(gh pr view $PR --repo $REPO --json comments \
--jq '.comments[].body' | grep "Post-deploy actions" || true)
if [ -n "$EXISTING" ]; then
echo "[post-deploy] outcome=\"already complete — post-deploy comment found\" status=success"
exit 0
fi
Step 1: Read PR Context
PR_DATA=$(gh pr view $PR --repo $REPO --json number,title,url,files,mergeCommit,baseRefName)
PR_TITLE=$(echo "$PR_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['title'])")
PR_URL=$(echo "$PR_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['url'])")
CHANGED_FILES=$(echo "$PR_DATA" | python3 -c "
import json,sys
d = json.load(sys.stdin)
print('\n'.join(f['path'] for f in d['files']))
")
echo "PR: $PR — $PR_TITLE"
echo "Changed files:"
echo "$CHANGED_FILES"
Step 2: Read Team Post-Deploy Configuration
Read the team's CLAUDE.md for post_deploy: rules. Each rule maps a file glob pattern to an action:
# Example in team CLAUDE.md:
post_deploy:
- match: "Dockerfile*"
action: ecr-rebuild
description: "Rebuild and push ECR image"
- match: "docker-entrypoint.sh"
action: ecr-rebuild
- match: "crew.yml"
action: config-reload-verify
description: "Verify gateway reloaded updated config"
- match: "scripts/*.sh"
action: executor-verify
description: "Spot-check executor behavior"
- match: "event-rules.yml"
action: config-reload-verify
- match: "**"
action: smoke-test
optional: true
description: "Optional smoke test for any PR"
Action types:
| Action | Description |
|---|---|
ecr-rebuild | Rebuild Docker image and push to ECR |
config-reload-verify | Verify the process/gateway has reloaded the updated config |
executor-verify | Spot-check executor or script behavior |
smoke-test | Run team-defined smoke test |
custom | Run command field as shell command |
Step 3: Match Files to Actions
# Pseudocode — implement in bash with Python for glob matching
import fnmatch
changed = CHANGED_FILES.splitlines()
triggered_actions = []
for rule in post_deploy_rules:
pattern = rule['match']
matching = [f for f in changed if fnmatch.fnmatch(f, pattern)]
if matching:
triggered_actions.append({
'action': rule['action'],
'description': rule.get('description', rule['action']),
'matched_files': matching,
'optional': rule.get('optional', False)
})
Step 4: Execute Actions
Run each triggered action. Document result for each.
Action: ecr-rebuild
# Rebuild and push ECR image
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
ECR_URI="${ECR_URI:-$(grep 'fargate_ecr_uri' $PYLOT_DIR/crew.yml | awk '{print $2}')}"
cd "$PYLOT_DIR"
bash scripts/build-worker-image.sh 2>&1
ECR_EXIT=$?
[ $ECR_EXIT -eq 0 ] && echo "ecr-rebuild: success" || echo "ecr-rebuild: failed exit=$ECR_EXIT"
Action: config-reload-verify
# Verify the executor/gateway has reloaded the updated config
# For Pylot: check that crew.yml timestamp matches what executor loaded
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
# Check if executor is running and has loaded recent config
CREW_MTIME=$(stat -c %Y "$PYLOT_DIR/crew.yml" 2>/dev/null || stat -f %m "$PYLOT_DIR/crew.yml" 2>/dev/null)
EXECUTOR_PID=$(pgrep -f "executor.sh" || true)
if [ -n "$EXECUTOR_PID" ]; then
echo "config-reload-verify: executor running (pid=$EXECUTOR_PID). crew.yml mtime=$CREW_MTIME."
echo "config-reload-verify: success — executor will pick up config on next poll cycle"
else
echo "config-reload-verify: WARNING — executor not running. Config will load on restart."
fi
Action: executor-verify
# Spot-check executor behavior — run a quick test
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
# Syntax-check the modified scripts
for script in $MATCHED_SCRIPTS; do
bash -n "$PYLOT_DIR/$script" && echo "executor-verify: $script syntax OK" || echo "executor-verify: $script SYNTAX ERROR"
done
Action: smoke-test
Run the team's configured smoke test. Optional by default.
# Team-specific smoke test — read from CLAUDE.md smoke_test.command
# Example for Pylot: run a dry-run event dispatch
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
bash "$PYLOT_DIR/event-router.sh" --dry-run 2>&1 | head -20
echo "smoke-test: event-router dry-run complete"
Step 5: Post Summary Comment
# Build table rows before heredoc (<<'EOF' prevents expansion — pre-build instead)
ACTION_TABLE=""
for action_entry in $TRIGGERED_ACTION_ENTRIES; do
ACTION_NAME=$(echo "$action_entry" | cut -d: -f1)
ACTION_FILES=$(echo "$action_entry" | cut -d: -f2)
ACTION_RESULT=$(echo "$action_entry" | cut -d: -f3)
ACTION_TABLE="${ACTION_TABLE}| ${ACTION_NAME} | ${ACTION_FILES} | ${ACTION_RESULT} |\n"
done
[ -z "$ACTION_TABLE" ] && ACTION_TABLE="_No file-match rules triggered for this PR's changed files._\n"
gh pr comment $PR --repo $REPO --body "$(printf '%s' "## Post-deploy actions
| Action | Matched Files | Result |
|--------|--------------|--------|
$(printf '%b' "$ACTION_TABLE")
**Pipeline complete.** This PR is live and verified.")"
Step 6: Write Report
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
REPORT="$PYLOT_DIR/reports/$(date +%Y-%m-%d)-post-deploy-$(echo $REPO | tr '/' '-')-pr${PR}.md"
cat > "$REPORT" <<EOF
# Post-Deploy: $REPO PR #$PR — $PR_TITLE
**Date**: $(date +%Y-%m-%d)
**PR**: [$REPO#$PR]($PR_URL)
## Changed Files
$CHANGED_FILES
## Actions Triggered
$TRIGGERED_ACTIONS_SUMMARY
## Results
$ACTIONS_RESULTS
EOF
Default File-Match Rules (Pylot)
| Pattern | Action | When |
|---|---|---|
Dockerfile* | ecr-rebuild | Docker image changed |
docker-entrypoint.sh | ecr-rebuild | Entrypoint script changed |
crew.yml | config-reload-verify | Crew config changed |
event-rules.yml | config-reload-verify | Event routing changed |
scripts/*.sh | executor-verify | Shell scripts changed |
dispatch.sh | executor-verify | Dispatch logic changed |
executor.sh | executor-verify | Executor logic changed |
Notes
- No matched rules = no actions. A PR touching only Markdown files does nothing. This is correct behavior.
- Optional actions (smoke-test) run but do not affect the summary verdict.
- ecr-rebuild failure should be surfaced as a comment but does not re-apply
deploy-failed— the deploy itself was verified. File a follow-up issue instead. - Team config is the authority. Generic skill + team CLAUDE.md = full configurability without modifying the skill.
- Monorepo: match files against per-target rules if the team has multiple deploy targets.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/fellowship-dev/dogfooded-skills/post-deploy">View post-deploy on skillZs</a>