Skip to content
Back to skills

Deploy

ASecurity

Review changes since last release, recommend version bump and release notes, then trigger CD pipeline

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 29, 2026
devopspythongobashrefactoringgitapidatabaseci/cddocumentation

Works with

  • claude code
  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned May 29, 2026

npx -y skills add runno-ai/chatnut --skill deploy --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Deploy?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Deploy
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/runno-ai-deploy/badge)](https://www.skillsdirectory.com/skills/runno-ai-deploy)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: deploy
title: Deploy & Release
description: Review changes since last release, recommend version bump and release notes, then trigger CD pipeline
---

# Deploy

Release controller for ChatNut. Reviews all changes since the last release, recommends a version bump, drafts release notes, and triggers the CD pipeline.

## Guards

1. **Must be on `main` branch** — `git branch --show-current` must be `main`. Abort if not.
2. **Working tree must be clean** — `git status --porcelain` must be empty. Abort if not.
3. **Must be in the chatnut repo** — check for `app/be/pyproject.toml`. Abort if not.
4. **Local main must match origin** — `git fetch origin main && git diff --quiet HEAD origin/main`. Abort if diverged.

## Workflow

### Step 1: Detect Last Release

```bash
LAST_TAG=$(gh release list --repo runno-ai/chatnut --limit 1 --json tagName -q '.[0].tagName')
if [ -z "$LAST_TAG" ]; then
  LAST_TAG=$(git rev-list --max-parents=0 HEAD)
fi
echo "Last release: $LAST_TAG"
```

### Step 2: Check for Retry State

Before gathering changes, check if we're in a "version bumped but release failed" state:

```bash
CURRENT_VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('app/be/pyproject.toml','rb'))['project']['version'])")
LAST_RELEASE_VERSION=${LAST_TAG#v}  # strip 'v' prefix

if [ "$CURRENT_VERSION" != "$LAST_RELEASE_VERSION" ]; then
  # Version in pyproject.toml doesn't match last release — check if tag exists
  if ! gh release view "v$CURRENT_VERSION" &>/dev/null; then
    echo "Detected: version $CURRENT_VERSION is bumped but not released."
    echo "Offering retry for v$CURRENT_VERSION..."
    # Skip to Step 4 with retry option
  fi
fi
```

If retry state detected, present user with:
- **Retry release for vX.Y.Z** — skip version bump, go straight to push + CI + CD
- **Start fresh** — analyze commits and recommend a new bump (overwriting the current version)
- **Abort**

### Step 3: Gather & Analyze Changes

```bash
git log "$LAST_TAG"..HEAD --format='%h %s' --no-merges
git diff "$LAST_TAG"..HEAD --stat
```

If **no commits since last release**, report "Nothing to release since $LAST_TAG" and stop.

Parse each commit subject by conventional commit prefix:

| Prefix | Category | Bump |
|--------|----------|------|
| `feat:` | Features | minor |
| `fix:` | Bug Fixes | patch |
| `refactor:` | Refactoring | patch |
| `docs:` | Documentation | — |
| `ci:` | CI/CD | — |
| `chore:` | Maintenance | — |
| `BREAKING CHANGE` or `!:` | Breaking | major |
| (no prefix) | Other | patch |

**Version bump recommendation** (highest wins):
- Any `BREAKING CHANGE` or `!:` → **major**
- Any `feat:` → **minor**
- Only `fix:`/`refactor:`/`chore:`/`docs:`/`ci:` → **patch**
- Only `docs:`/`ci:` → suggest skipping release (no functional changes)

**Compute next version:** Read current version from `app/be/pyproject.toml`, apply the recommended bump. Example: `0.3.0` + minor → `0.4.0`.

### Step 4: Present to User

Show:
1. **Changes since last release** — grouped by category
2. **Recommended version bump** — e.g., `0.3.0 → 0.4.0 (minor: new features)`
3. **Draft release notes** — categorized commit list

Use `AskUserQuestion` to let the user:
- Accept the recommended version
- Override with a different version
- Abort the release

### Step 5: Execute Release

After user confirms:

1. **Bump version** in `app/be/pyproject.toml` — set to the confirmed version
2. **Commit**: `chore: bump version to X.Y.Z`
3. **Push** to `main`
4. **Wait for CI** — poll `gh run list --workflow=ci.yml --branch main --commit $(git rev-parse HEAD)` until conclusion is `success` or `failure`. If CI fails, report and stop (version bump is on main but release is aborted — next `/deployment` run will detect the retry state in Step 2).
5. **Trigger CD**: `gh workflow run cd.yml --repo runno-ai/chatnut --ref main`
6. **Wait for CD** — `gh run watch` blocks until the CD run completes:
   ```bash
   # Get the run ID (latest CD run on main)
   sleep 5  # brief wait for run to appear
   RUN_ID=$(gh run list --workflow=cd.yml --branch main --limit 1 --json databaseId -q '.[0].databaseId')
   gh run watch "$RUN_ID" --exit-status
   ```
   If CD fails, report the failure and stop. The version bump commit is on main — next `/deployment` run will detect the retry state in Step 2.
7. **Verify** (only after CD completes successfully):
   - `gh release view vX.Y.Z --repo runno-ai/chatnut` — confirm GitHub Release exists
   - `python3 -c "import urllib.request; urllib.request.urlopen('https://pypi.org/pypi/chatnut/X.Y.Z/json'); print('Published to PyPI')"` — confirm PyPI availability
8. **Update local install** — reinstall the global CLI from PyPI so the local `chatnut` matches the released version:
   ```bash
   uv tool install --force chatnut
   chatnut serve &  # restart server with new version
   sleep 2
   curl -s "http://127.0.0.1:$(cat ~/.chatnut/server.port)/api/status"
   ```
   If the install fails, report but don't block — the release itself succeeded.

## Error Handling

- **Not on main**: "Switch to main branch first. Aborting."
- **Dirty working tree**: "Uncommitted changes detected. Commit or stash first."
- **Local diverged from origin**: "Local main differs from origin/main. Pull first."
- **No changes since last release**: "Nothing to release since vX.Y.Z."
- **CI fails after push**: "CI failed. Version bump is on main but release aborted. Run `/deployment` again to retry."
- **CD fails**: "CD failed. Version bump is on main but release not published. Run `/deployment` again to retry."
- **CD trigger fails**: Report error, don't retry.
- **Push fails**: Report error, don't retry (follows push-once rule).

## Handling Bad Releases

PyPI versions are immutable — you cannot delete or overwrite a published version. If a bad version is released:

1. Fix the bug on main
2. Run `/deployment` — it will recommend a patch bump (e.g., `1.2.3` → `1.2.4`)
3. The patch release supersedes the bad version

## SDK Deployment (`/deployment sdk`)

When called with argument `sdk`, runs the SDK release pipeline instead of the
platform deployment:

1. **Verify the SDK subtree is clean:**
   ```bash
   cd /Users/tushuyang/runno/main
   git diff HEAD -- crates/runno-agent-sdk/ | head -20
   ```
   If uncommitted changes exist, stop and ask the user to commit first.

2. **Gather & analyze changes since last SDK release:**
   ```bash
   LAST_SDK_TAG=$(git tag -l 'sdk-v*' --sort=-v:refname | head -1)
   echo "Last SDK release: $LAST_SDK_TAG"
   git log "$LAST_SDK_TAG"..HEAD --format='%h %s' -- crates/runno-agent-sdk/
   git diff "$LAST_SDK_TAG"..HEAD --stat -- crates/runno-agent-sdk/
   ```
   If **no commits since last tag**, report "Nothing to release since $LAST_SDK_TAG" and stop.

   Parse each commit using the same conventional commit rules as the platform workflow
   (feat→minor, fix→patch, breaking→major, docs/ci/chore→skip). Compute the recommended
   bump from the highest-priority category.

3. **Present changes and recommend version:**
   - Read the current version from `crates/runno-agent-sdk/Cargo.toml` → `[package].version`
   - Show the categorized commit list and file diff stats
   - Present the recommended bump with `AskUserQuestion` (accept / override / abort)
   - Apply the confirmed bump to `Cargo.toml` (and any sub-crate `Cargo.toml` files that pin their own version)

4. **Tag and push:**
   ```bash
   TAG="sdk-v$(grep '^version' crates/runno-agent-sdk/Cargo.toml | head -1 | grep -oP '[\d.]+')"
   git tag "$TAG"
   git push origin "$TAG"
   ```

5. **Monitor the release workflow:**
   ```bash
   gh run watch --repo runno-ai/runno $(gh run list --workflow sdk-release.yml --limit 1 --json databaseId --jq '.[0].databaseId')
   ```

6. **Confirm success:** Check that the release appears on `runno-ai/runno-agent-sdk`.

7. **Rebuild and reinstall the `agent-mcp-server` binary (smart — skips if already current):**
   ```bash
   ~/.claude/skills/deployment/build-mcp-binary.sh "$VERSION"
   ```
   The script checks the installed binary's version first and skips the Cargo build if it already
   matches. If the build fails, report the error but don't block — the SDK release itself succeeded.

   **Note:** Claude Code must be restarted to pick up the new binary.

Files in this skill

  • SKILL.md8.1 KB
  • build-mcp-binary.sh3.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…