# Release Sync

> Audit and update Argus external distribution surfaces (Spring Boot starter, Helm chart, Docker, install scripts, Homebrew Formula, SDKMAN, GitHub Actions, action.yml) against gradle.properties and known-good upstream versions. Detects version drift, floating image tags, deprecated K8s APIs, and outdated dependencies. Use before a release, or when the user asks to "sync release", "update distributions", "check outdated deps", "verify external integrations".

- Skill: `rlaope/release-sync` (Agent Skill)
- Install (CLI): `npx skillmds@latest add rlaope/release-sync`
- Raw SKILL.md: https://api.skillmd.com/api/skills/rlaope/release-sync/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: rlaope (https://skillmd.com/u/rlaope)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/rlaope/release-sync

---


# release-sync

Keep every Argus distribution channel aligned with the project version and free of stale dependencies.

## When to use

- Before tagging a release
- After bumping `argusVersion` in `gradle.properties`
- Periodic outdated-dependency sweep (Netty CVEs, Spring Boot, Micrometer)
- User says: "sync release", "release-sync", "check outdated", "verify distributions", "/release-sync"

## Authoritative sources

| Truth | Path |
|---|---|
| Project version | `gradle.properties` → `argusVersion` |
| Pinned framework versions | `gradle.properties` → `nettyVersion`, `junitVersion` |
| Module list | `settings.gradle.kts` |
| Java baseline | root `build.gradle.kts` toolchain |

## Targets

### Channel inventory

| Channel | Files | Version surface |
|---|---|---|
| Maven Central (starter) | `argus-spring-boot-starter/build.gradle.kts` | Spring Boot, Spring Context, Micrometer, configuration-processor versions |
| Helm chart | `charts/argus/Chart.yaml`, `charts/argus/values.yaml`, `charts/argus/templates/*.yaml`, `charts/argus/README.md` | `version`, `appVersion`, `kubeVersion`, image tag, K8s API versions |
| Docker compose | `deploy/docker-compose.yml` | image tags (must NOT be `:latest`) |
| Dockerfiles | `Dockerfile`, `deploy/docker/Dockerfile.*` (production only; `*.example` files use `:latest` deliberately as templates) | base image tag (e.g., `eclipse-temurin:21-jre-alpine`) |
| Install (Unix) | `install.sh` | `VERSION` fallback, `ASPROF_VERSION`, checksum verification logic |
| Install (Windows) | `install.ps1` | `Version` fallback, example block |
| Homebrew | `Formula/argus.rb` | `version`, `url`, `sha256`, `depends_on` JDK version |
| SDKMAN | `deploy/sdkman/argus-candidate.json`, `deploy/sdkman/README.md` | candidate version |
| GitHub Action | `action/action.yml` | `version` input default |
| CI workflows | `.github/workflows/{ci,release,docker,native-image,pages}.yml` | `actions/checkout@v?`, `actions/setup-java@v?`, JDK version, runner |

### Upstream "known-good" baselines (update this table when bumping)

These are the current stable minima. Refresh only when the user asks for a sweep.

| Library / image | Minimum acceptable | Why |
|---|---|---|
| Netty | `4.1.115.Final` | CVE-2024-47535 patched here |
| JUnit Jupiter | `5.11.x` | Bug fixes vs 5.10 |
| Spring Boot (compileOnly) | `3.2.0` (LTS-equivalent OK) | Argus targets Spring Boot 3.2+ |
| Micrometer | `1.12.0` (`1.13.x+` preferred for VT metrics) | Virtual Thread metric surface |
| `actions/checkout` | `v4` | v3 deprecated track |
| `actions/setup-java` | `v4` | v3 deprecated track |
| `eclipse-temurin` base | `21-jre-alpine` | Project Java baseline |
| `prom/prometheus` | a pinned `vX.Y.Z`, never `latest` | reproducibility |
| `grafana/grafana` | a pinned `X.Y.Z`, never `latest` | reproducibility |
| Kubernetes API | `apps/v1`, `networking.k8s.io/v1`, `monitoring.coreos.com/v1` | K8s 1.25+ removed `*beta1` variants |
| async-profiler | install-time and runtime versions MUST match — see §2.4 cross-source check | install.sh and `AsProfDownloader` write to the same `~/.argus/lib/async-profiler/` directory; a mismatch silently makes one of the two payloads dead weight |

## Procedure

### 1. Resolve truth

```bash
ARGUS_VERSION="$(awk -F= '/^argusVersion=/{print $2}' gradle.properties)"
NETTY="$(awk -F= '/^nettyVersion=/{print $2}' gradle.properties)"
JUNIT="$(awk -F= '/^junitVersion=/{print $2}' gradle.properties)"
echo "TRUTH version=$ARGUS_VERSION netty=$NETTY junit=$JUNIT"
```

### 2. Per-channel drift checks

#### 2.1 Helm chart

```bash
grep -E '^(version|appVersion|kubeVersion):' charts/argus/Chart.yaml
```

- `version` and `appVersion` MUST equal `$ARGUS_VERSION` (appVersion in quotes).
- `kubeVersion` MUST be present, e.g. `>=1.23.0-0`.
- Grep templates for deprecated APIs:
  ```bash
  grep -rEn 'apiVersion: (extensions/v1beta1|policy/v1beta1|networking\.k8s\.io/v1beta1)' charts/argus/templates/
  ```
  Any hit is a P0 — those resources won't apply on K8s 1.25+.
- Verify `image.tag` in `values.yaml` matches `$ARGUS_VERSION` (or is omitted to inherit appVersion).

#### 2.2 Docker compose

```bash
grep -nE 'image:\s*[^[:space:]]+:latest' deploy/docker-compose.yml
```
Any hit is a P0 — pin to a concrete version.

#### 2.3 Dockerfiles

```bash
grep -rEn '^FROM ' Dockerfile deploy/docker/
```
Confirm base image is `eclipse-temurin:21-jre-alpine` (or the agreed baseline). Flag any divergence.

#### 2.4 install.sh / install.ps1

```bash
grep -nE "VERSION=\"v|fallback|Version = \"v" install.sh install.ps1
```
- Fallback string MUST be `v$ARGUS_VERSION`.
- `install.sh` example URLs in the comment header use a recent version.

**async-profiler cross-source check** (this is the trap that the v1.4.0 verify pass caught — install.sh's `ASPROF_VERSION="3.0"` shipped for releases while runtime was already pinned to `4.4`, making the 267 KB install-time download dead weight):

```bash
INSTALL_ASPROF=$(awk -F'"' '/^ASPROF_VERSION=/{print $2}' install.sh)
RUNTIME_ASPROF_DL=$(grep -E '^\s*private static final String ASPROF_VERSION' \
    argus-cli/src/main/java/io/argus/cli/provider/jdk/AsProfDownloader.java \
    | awk -F'"' '{print $2}')
RUNTIME_ASPROF_CAP=$(grep -E '^\s*public static final String ASPROF_VERSION' \
    argus-cli/src/main/java/io/argus/cli/provider/jdk/AsProfCapabilities.java \
    | awk -F'"' '{print $2}')

echo "install.sh             = $INSTALL_ASPROF"
echo "AsProfDownloader.java  = $RUNTIME_ASPROF_DL"
echo "AsProfCapabilities.java = $RUNTIME_ASPROF_CAP"

[ "$INSTALL_ASPROF" = "$RUNTIME_ASPROF_DL" ] && [ "$RUNTIME_ASPROF_DL" = "$RUNTIME_ASPROF_CAP" ] \
    && echo "OK — all three async-profiler version constants agree" \
    || echo "DRIFT — install.sh and runtime async-profiler versions disagree (P1)"
```

All three values MUST be identical. They share `~/.argus/lib/async-profiler/`, so a mismatch means whichever source ran last wins and the other download was wasted bandwidth — or worse, the binary on disk lies about its version. If runtime side bumps, install.sh MUST follow in the same release.

#### 2.5 Homebrew Formula

```bash
grep -E "(version|url|sha256)" Formula/argus.rb
```
- `version` matches `$ARGUS_VERSION`.
- `url` points at the GitHub release for that version.
- `sha256` matches the actual JAR/binary on the release; if updating version, recompute:
  ```bash
  curl -fsSL "<url>" | shasum -a 256
  ```
  Never invent a hash — compute or escalate.

#### 2.6 SDKMAN

```bash
grep -E '"version"|"url"' deploy/sdkman/argus-candidate.json
```
Version field equals `$ARGUS_VERSION`.

#### 2.7 GitHub Action

```bash
grep -nE '(version|default):' action/action.yml
```
`version` input default should be `$ARGUS_VERSION` or `latest` — confirm with user which convention this repo uses.

#### 2.8 CI workflows

```bash
grep -rnE 'uses: [a-z-]+/[a-z-]+@v[0-9]+' .github/workflows/
grep -rnE 'java-version: ' .github/workflows/
```
Compare action @vN against the baseline table. JDK version should match the project toolchain (21).

#### 2.9 Spring Boot starter dependencies

```bash
grep -E "compileOnly|annotationProcessor|api\(" argus-spring-boot-starter/build.gradle.kts
```
Compare against baseline minima above. Spring Boot can stay at `3.2.0` if intentional, but flag `< 3.2.0` as a hard fail.

#### 2.10 Outdated framework versions in gradle.properties

```bash
cat gradle.properties
```
Compare to baseline minima. Netty `< 4.1.115.Final` is a P0 (CVE).

#### 2.11 Post-release artifact verification

A green `release.yml` run is not proof every distribution channel succeeded. Each tag-triggered workflow has independent jobs, and one can fail silently while docs continue to reference the URL/tag that was supposed to be produced. Always cross-check the *artifacts*, not just the run status.

For the most recent published tag (or the tag passed by the user), verify each channel:

```bash
LATEST_TAG="$(gh release view --json tagName -q .tagName)"

# 1. Per-workflow conclusion for that tag
for WF in release.yml docker.yml native-image.yml; do
  echo "=== $WF ==="
  gh run list --workflow "$WF" --branch "$LATEST_TAG" --limit 3 \
    --json conclusion,headBranch,event,name,databaseId,createdAt
done

# 2. Per-job conclusion (catch the case where the run is "failure" but only one job)
RUN_ID=$(gh run list --workflow docker.yml --branch "$LATEST_TAG" --limit 1 --json databaseId -q '.[0].databaseId')
gh run view "$RUN_ID" --json jobs -q '.jobs[] | "\(.name): \(.conclusion)"'

# 3. GHCR images actually exist (anonymous manifest fetch — works for public packages)
VERSION="${LATEST_TAG#v}"
OWNER_LC=$(echo "$GITHUB_REPOSITORY_OWNER" | tr 'A-Z' 'a-z')   # GHCR is case-sensitive lowercase
for IMAGE in argus argus-agent; do
  TOKEN=$(curl -s "https://ghcr.io/token?scope=repository:${OWNER_LC}/${IMAGE}:pull" | jq -r .token)
  HTTP=$(curl -s -o /dev/null -w "%{http_code}" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json" \
    "https://ghcr.io/v2/${OWNER_LC}/${IMAGE}/manifests/${VERSION}")
  echo "ghcr.io/${OWNER_LC}/${IMAGE}:${VERSION} -> HTTP $HTTP"
done
# 200 = exists, 404 = missing → P0 if docs/Helm reference it.

# 4. GitHub Release artifacts present
gh release view "$LATEST_TAG" --json assets -q '.assets[].name'
```

Expected for a healthy 1.x release:
- `release.yml`: success, with `argus-agent.jar`, `argus-server.jar`, `argus-cli-X.Y.Z-all.jar` listed under release assets.
- `docker.yml`: every job (`Build CLI image`, `Build agent image`) success. Manifest fetch returns HTTP 200 for both `argus` and `argus-agent`.
- `native-image.yml`: success if the project ships native binaries; if the upload step fails, `install.sh` falls back to JAR (which is fine but flag it so the user knows their native artifact is missing).

**Red flags this check is meant to catch:**
- A run is marked `failure` but only one of N jobs failed, and the published-version assumption silently breaks (e.g., agent image missing while CLI was fine).
- Workflow succeeded but `gh release view` shows no assets attached (action-gh-release misconfig).
- A `latest` tag in GHCR points to a stale digest because the new push was rejected.

If a channel is missing for the current `argusVersion`:
1. Identify the failing job from `gh run view --log-failed`.
2. Fix the workflow on a branch (do not retag `vX.Y.Z` in place — Git tags are immutable on consumers' machines).
3. After the fix is merged to `master`, re-run via `workflow_dispatch` against the existing tag, or cut a `vX.Y.(Z+1)` patch release.

### 3. Fix policy

- **Pure version swaps** with a clear baseline (Helm appVersion, install fallback, compose pin): edit directly.
- **Dependency bumps** that touch `gradle.properties` or starter `build.gradle.kts`: run `./gradlew compileJava` and `./gradlew :argus-cli:test` after the edit; revert if it breaks.
- **Homebrew `sha256`**: never guess. Either fetch the release artifact and compute, or escalate.
- **Deprecated K8s APIs**: replace per K8s migration guide; do not silently delete a resource.
- **GitHub Action default version**: ask the user — some projects pin to specific tags, some prefer `latest`.

### 4. Verify

```bash
./gradlew compileJava --quiet         # bumps don't break compilation
./gradlew :argus-cli:test --quiet     # tests still pass
helm lint charts/argus                # if helm is on PATH
docker compose -f deploy/docker-compose.yml config >/dev/null   # compose syntax OK
```

## Output format

```
release-sync report
===================
Truth:  version=1.2.0  netty=4.1.115.Final  junit=5.11.4

Channel matrix:
  starter      : OK     (Spring Boot 3.2.0 compileOnly, Micrometer 1.12.0)
  helm         : FIXED  Chart.yaml +kubeVersion: ">=1.23.0-0"
  docker       : FIXED  prom/prometheus:latest → :v2.55.0; grafana/grafana:latest → 11.3.0
  install.sh   : FIXED  fallback v1.1.0 → v1.2.0
  install.ps1  : FIXED  fallback v0.4.0 → v1.2.0; example block
  formula      : OK
  sdkman       : OK
  action.yml   : OK     (default version: latest, by design)
  ci-workflows : OK     (actions @v4, JDK 21)

Dep bumps:
  netty 4.1.104.Final → 4.1.115.Final  (CVE-2024-47535)
  junit 5.10.1        → 5.11.4

Verification:
  ./gradlew compileJava → exit 0
  ./gradlew :argus-cli:test → exit 0

Released-tag verification (v1.2.0):
  release.yml                       : success
  docker.yml / Build CLI image      : success
  docker.yml / Build agent image    : FAILURE  ← P0
  native-image.yml                  : success
  ghcr.io/<owner>/argus:1.2.0       : 200 OK
  ghcr.io/<owner>/argus-agent:1.2.0 : 404 missing  ← P0
  release assets                    : argus-cli-1.2.0-all.jar, argus-agent.jar, argus-server.jar

Escalated to user:
  - docker.yml build-agent job failed for v1.2.0 — agent image is not on GHCR even though docs reference it. Fix workflow, then re-run via workflow_dispatch on the v1.2.0 tag or cut v1.2.1.
  - Homebrew Formula sha256 — release artifact not yet uploaded; rerun after release publish.
```

## Project rules to honor

- Never commit to master directly — finish on a branch and propose a PR.
- Don't push tags; the release workflow handles that on a tag push.
- Don't bump `argusVersion` from this skill — that belongs to the release flow.
- Never write `--no-verify` or skip hooks.
- Don't invent SHA256 or release URLs; compute them or escalate.

## Out of scope

- Editing user-facing prose / READMEs / site copy → use `docs-sync`.
- Publishing to Maven Central (signing, Sonatype upload) — release workflow only.
- Generating SBOM / SLSA attestation — separate concern; flag if missing but don't implement here.

