From c27444a28c92a331ec4899d812ef10f6e0accd4b Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sat, 2 May 2026 16:52:01 -0700 Subject: [PATCH 01/10] Skip empty releases in code tag workflow --- .github/workflows/code-tag.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/code-tag.yml b/.github/workflows/code-tag.yml index 44bd3d2f4..ea388329b 100644 --- a/.github/workflows/code-tag.yml +++ b/.github/workflows/code-tag.yml @@ -65,6 +65,23 @@ jobs: NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" TAG="v$NEW_VERSION" + + # Skip if this tag already exists (no new commits since last release) + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists. Nothing new to release." + exit 0 + fi + + # Check that code app actually changed since the last release tag + LAST_RELEASE=$(git tag --list "v${MAJOR}.${MINOR}.*" --sort=-v:refname | head -1) + CODE_PATHS="apps/code apps/cli packages/agent packages/core packages/shared packages/electron-trpc pnpm-lock.yaml" + CHANGES=$(git diff --name-only "$LAST_RELEASE"..HEAD -- $CODE_PATHS) + + if [ -z "$CHANGES" ]; then + echo "No relevant changes since $LAST_RELEASE. Skipping release." + exit 0 + fi + echo "Creating tag: $TAG (from base tag $LATEST_TAG + $PATCH commits)" git config user.email "github-actions[bot]@users.noreply.github.com" From 07428390e13f1e4a16816d8e0434799ddd598c3f Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sat, 2 May 2026 16:54:33 -0700 Subject: [PATCH 02/10] Skip duplicate tags in code-tag workflow --- .github/workflows/code-tag.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/code-tag.yml b/.github/workflows/code-tag.yml index ea388329b..942e0231b 100644 --- a/.github/workflows/code-tag.yml +++ b/.github/workflows/code-tag.yml @@ -72,16 +72,6 @@ jobs: exit 0 fi - # Check that code app actually changed since the last release tag - LAST_RELEASE=$(git tag --list "v${MAJOR}.${MINOR}.*" --sort=-v:refname | head -1) - CODE_PATHS="apps/code apps/cli packages/agent packages/core packages/shared packages/electron-trpc pnpm-lock.yaml" - CHANGES=$(git diff --name-only "$LAST_RELEASE"..HEAD -- $CODE_PATHS) - - if [ -z "$CHANGES" ]; then - echo "No relevant changes since $LAST_RELEASE. Skipping release." - exit 0 - fi - echo "Creating tag: $TAG (from base tag $LATEST_TAG + $PATCH commits)" git config user.email "github-actions[bot]@users.noreply.github.com" From 18a26bfd921c3d9fe464b3024b5ff4297113964c Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sat, 2 May 2026 17:25:26 -0700 Subject: [PATCH 03/10] Add quiet period check to code-tag workflow --- .github/workflows/code-tag.yml | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/workflows/code-tag.yml b/.github/workflows/code-tag.yml index 942e0231b..bb7e12c04 100644 --- a/.github/workflows/code-tag.yml +++ b/.github/workflows/code-tag.yml @@ -4,6 +4,10 @@ on: schedule: - cron: "0 1,17 * * *" workflow_dispatch: + inputs: + quiet_retries: + type: number + default: 0 pull_request: types: [closed] branches: @@ -37,7 +41,39 @@ jobs: fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} + - name: Check quiet period + id: quiet + if: github.event_name == 'schedule' || github.event.inputs.quiet_retries > 0 + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + RETRIES=${{ github.event.inputs.quiet_retries || 0 }} + MAX_RETRIES=3 + LAST_COMMIT_EPOCH=$(git log -1 --format=%ct HEAD) + NOW_EPOCH=$(date +%s) + AGE_MINUTES=$(( (NOW_EPOCH - LAST_COMMIT_EPOCH) / 60 )) + + if [ "$AGE_MINUTES" -ge 15 ]; then + echo "Last commit to main was ${AGE_MINUTES}m ago. Quiet period met." + echo "proceed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "$RETRIES" -ge "$MAX_RETRIES" ]; then + echo "Retried ${MAX_RETRIES} times and commits are still landing. Proceeding anyway." + echo "proceed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + NEXT=$((RETRIES + 1)) + WAIT=$((15 - AGE_MINUTES)) + echo "Last commit was ${AGE_MINUTES}m ago (< 15m). Waiting ${WAIT}m then retrying (${NEXT}/${MAX_RETRIES})..." + sleep "${WAIT}m" + gh workflow run code-tag.yml -f quiet_retries="$NEXT" + echo "proceed=false" >> "$GITHUB_OUTPUT" + - name: Compute version and create tag + if: steps.quiet.outputs.proceed != 'false' env: GH_TOKEN: ${{ steps.app-token.outputs.token }} REPOSITORY: ${{ github.repository }} From e1e92b409119044392e4327c1eeaae90e1fd18e3 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 3 May 2026 00:53:33 -0700 Subject: [PATCH 04/10] Fix injection and extract release tag logic with tests --- .github/scripts/compute-release-tag.sh | 41 ++++++ .github/scripts/compute-release-tag.test.sh | 154 ++++++++++++++++++++ .github/workflows/code-tag.yml | 38 +---- 3 files changed, 202 insertions(+), 31 deletions(-) create mode 100755 .github/scripts/compute-release-tag.sh create mode 100755 .github/scripts/compute-release-tag.test.sh diff --git a/.github/scripts/compute-release-tag.sh b/.github/scripts/compute-release-tag.sh new file mode 100755 index 000000000..648b32cbd --- /dev/null +++ b/.github/scripts/compute-release-tag.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Computes the next patch release tag based on commits since the latest base tag. +# +# Stdout (KEY=VALUE lines when a new tag should be created): +# tag=vX.Y.Z +# base_tag=vX.Y +# +# Exit codes: +# 0 - success (empty stdout means nothing to release) +# 1 - no base version tag found + +LATEST_TAG=$(git tag --list 'v[0-9]*.[0-9]*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+(\.0)?$' | head -1 || true) + +if [ -z "$LATEST_TAG" ]; then + echo "No version tag found. Create one with: git tag v0.15 && git push origin v0.15" >&2 + exit 1 +fi + +VERSION_PART=${LATEST_TAG#v} +MAJOR=$(echo "$VERSION_PART" | cut -d. -f1) +MINOR=$(echo "$VERSION_PART" | cut -d. -f2) + +PATCH=$(git rev-list "$LATEST_TAG"..HEAD --count) + +if [ "$PATCH" -eq 0 ]; then + echo "No commits since $LATEST_TAG. Nothing to release." >&2 + exit 0 +fi + +NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" +TAG="v$NEW_VERSION" + +if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists. Nothing new to release." >&2 + exit 0 +fi + +echo "tag=$TAG" +echo "base_tag=$LATEST_TAG" diff --git a/.github/scripts/compute-release-tag.test.sh b/.github/scripts/compute-release-tag.test.sh new file mode 100755 index 000000000..a48fbbac8 --- /dev/null +++ b/.github/scripts/compute-release-tag.test.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$SCRIPT_DIR/compute-release-tag.sh" +ORIGINAL_DIR="$PWD" +PASS=0 +FAIL=0 + +setup_repo() { + TEST_DIR=$(mktemp -d) + cd "$TEST_DIR" + git init -q + git config user.email "test@test.com" + git config user.name "Test" + git commit --allow-empty -m "init" -q +} + +teardown_repo() { + cd "$ORIGINAL_DIR" + rm -rf "$TEST_DIR" +} + +run_script() { + set +e + bash "$SCRIPT" >"$TMPDIR/crt-stdout" 2>"$TMPDIR/crt-stderr" + LAST_EXIT=$? + set -e + LAST_STDOUT=$(cat "$TMPDIR/crt-stdout") + LAST_STDERR=$(cat "$TMPDIR/crt-stderr") +} + +assert_eq() { + local actual="$1" expected="$2" name="$3" + if [ "$actual" = "$expected" ]; then + echo " PASS: $name" + PASS=$((PASS + 1)) + else + echo " FAIL: $name" + echo " expected: $expected" + echo " actual: $actual" + FAIL=$((FAIL + 1)) + fi +} + +assert_contains() { + local haystack="$1" needle="$2" name="$3" + if echo "$haystack" | grep -qF "$needle"; then + echo " PASS: $name" + PASS=$((PASS + 1)) + else + echo " FAIL: $name" + echo " expected to contain: $needle" + echo " got: $haystack" + FAIL=$((FAIL + 1)) + fi +} + +echo "compute-release-tag.sh" +echo "" + +# ── No base tag ─────────────────────────────────────── +echo "no base tag:" +setup_repo +run_script +assert_eq "$LAST_EXIT" "1" "exits 1" +assert_contains "$LAST_STDERR" "No version tag found" "prints error to stderr" +assert_eq "$LAST_STDOUT" "" "no stdout" +teardown_repo + +# ── No commits since tag ────────────────────────────── +echo "no commits since tag:" +setup_repo +git tag v1.0 +run_script +assert_eq "$LAST_EXIT" "0" "exits 0" +assert_eq "$LAST_STDOUT" "" "no stdout" +assert_contains "$LAST_STDERR" "Nothing to release" "prints skip message" +teardown_repo + +# ── Normal patch bump ───────────────────────────────── +echo "normal patch bump (2 commits):" +setup_repo +git tag v1.0 +git commit --allow-empty -m "feat 1" -q +git commit --allow-empty -m "feat 2" -q +run_script +assert_eq "$LAST_EXIT" "0" "exits 0" +assert_contains "$LAST_STDOUT" "tag=v1.0.2" "computes v1.0.2" +assert_contains "$LAST_STDOUT" "base_tag=v1.0" "reports base tag" +teardown_repo + +# ── Single commit ───────────────────────────────────── +echo "single commit:" +setup_repo +git tag v3.2 +git commit --allow-empty -m "fix" -q +run_script +assert_eq "$LAST_EXIT" "0" "exits 0" +assert_contains "$LAST_STDOUT" "tag=v3.2.1" "computes v3.2.1" +teardown_repo + +# ── Duplicate tag ───────────────────────────────────── +echo "duplicate tag already exists:" +setup_repo +git tag v1.0 +git commit --allow-empty -m "feat" -q +git tag v1.0.1 +run_script +assert_eq "$LAST_EXIT" "0" "exits 0" +assert_eq "$LAST_STDOUT" "" "no stdout" +assert_contains "$LAST_STDERR" "already exists" "prints skip message" +teardown_repo + +# ── vX.Y.0 base tag format ─────────────────────────── +echo "vX.Y.0 base tag format:" +setup_repo +git tag v2.5.0 +git commit --allow-empty -m "feat" -q +run_script +assert_eq "$LAST_EXIT" "0" "exits 0" +assert_contains "$LAST_STDOUT" "tag=v2.5.1" "computes v2.5.1 from v2.5.0 base" +teardown_repo + +# ── Picks latest base tag ──────────────────────────── +echo "picks latest base tag when multiple exist:" +setup_repo +git tag v1.0 +git commit --allow-empty -m "a" -q +git tag v1.1 +git commit --allow-empty -m "b" -q +run_script +assert_eq "$LAST_EXIT" "0" "exits 0" +assert_contains "$LAST_STDOUT" "tag=v1.1.1" "uses latest base for version" +assert_contains "$LAST_STDOUT" "base_tag=v1.1" "reports v1.1 as base" +teardown_repo + +# ── Ignores non-base patch tags ────────────────────── +echo "ignores existing patch tags as base:" +setup_repo +git tag v1.0 +git commit --allow-empty -m "a" -q +git tag v1.0.1 +git commit --allow-empty -m "b" -q +git commit --allow-empty -m "c" -q +run_script +assert_eq "$LAST_EXIT" "0" "exits 0" +assert_contains "$LAST_STDOUT" "tag=v1.0.3" "counts all commits since base, not since patch" +assert_contains "$LAST_STDOUT" "base_tag=v1.0" "base is v1.0 not v1.0.1" +teardown_repo + +echo "" +echo "Results: ${PASS} passed, ${FAIL} failed" +[ "$FAIL" -eq 0 ] || exit 1 diff --git a/.github/workflows/code-tag.yml b/.github/workflows/code-tag.yml index bb7e12c04..783ad70a4 100644 --- a/.github/workflows/code-tag.yml +++ b/.github/workflows/code-tag.yml @@ -46,8 +46,9 @@ jobs: if: github.event_name == 'schedule' || github.event.inputs.quiet_retries > 0 env: GH_TOKEN: ${{ steps.app-token.outputs.token }} + QUIET_RETRIES: ${{ github.event.inputs.quiet_retries || 0 }} run: | - RETRIES=${{ github.event.inputs.quiet_retries || 0 }} + RETRIES="${QUIET_RETRIES}" MAX_RETRIES=3 LAST_COMMIT_EPOCH=$(git log -1 --format=%ct HEAD) NOW_EPOCH=$(date +%s) @@ -66,9 +67,7 @@ jobs: fi NEXT=$((RETRIES + 1)) - WAIT=$((15 - AGE_MINUTES)) - echo "Last commit was ${AGE_MINUTES}m ago (< 15m). Waiting ${WAIT}m then retrying (${NEXT}/${MAX_RETRIES})..." - sleep "${WAIT}m" + echo "Last commit was ${AGE_MINUTES}m ago (< 15m). Re-dispatching retry ${NEXT}/${MAX_RETRIES}..." gh workflow run code-tag.yml -f quiet_retries="$NEXT" echo "proceed=false" >> "$GITHUB_OUTPUT" @@ -78,37 +77,14 @@ jobs: GH_TOKEN: ${{ steps.app-token.outputs.token }} REPOSITORY: ${{ github.repository }} run: | - # Find the latest base version tag (vX.Y or vX.Y.0 format) - LATEST_TAG=$(git tag --list 'v[0-9]*.[0-9]*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+(\.0)?$' | head -1) + RESULT=$(.github/scripts/compute-release-tag.sh) - if [ -z "$LATEST_TAG" ]; then - echo "No version tag found. Create one with: git tag v0.15 && git push origin v0.15" - exit 1 - fi - - # Extract major.minor from tag - VERSION_PART=${LATEST_TAG#v} - MAJOR=$(echo "$VERSION_PART" | cut -d. -f1) - MINOR=$(echo "$VERSION_PART" | cut -d. -f2) - - # Count commits since the base tag - PATCH=$(git rev-list "$LATEST_TAG"..HEAD --count) - - if [ "$PATCH" -eq 0 ]; then - echo "No commits since $LATEST_TAG. Nothing to release." - exit 0 - fi - - NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" - TAG="v$NEW_VERSION" - - # Skip if this tag already exists (no new commits since last release) - if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists. Nothing new to release." + if [ -z "$RESULT" ]; then exit 0 fi - echo "Creating tag: $TAG (from base tag $LATEST_TAG + $PATCH commits)" + TAG=$(echo "$RESULT" | grep '^tag=' | cut -d= -f2) + echo "Creating tag: $TAG" git config user.email "github-actions[bot]@users.noreply.github.com" git config user.name "github-actions[bot]" From e1a8abf4adf284c34fcc8534b7f5c85b98432248 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 3 May 2026 00:56:37 -0700 Subject: [PATCH 05/10] Harden test cleanup and TMPDIR fallback --- .github/scripts/compute-release-tag.test.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/scripts/compute-release-tag.test.sh b/.github/scripts/compute-release-tag.test.sh index a48fbbac8..17f211bc4 100755 --- a/.github/scripts/compute-release-tag.test.sh +++ b/.github/scripts/compute-release-tag.test.sh @@ -6,6 +6,9 @@ SCRIPT="$SCRIPT_DIR/compute-release-tag.sh" ORIGINAL_DIR="$PWD" PASS=0 FAIL=0 +TEST_DIR="" + +trap 'cd "$ORIGINAL_DIR"; [ -n "$TEST_DIR" ] && rm -rf "$TEST_DIR"' EXIT setup_repo() { TEST_DIR=$(mktemp -d) @@ -19,15 +22,17 @@ setup_repo() { teardown_repo() { cd "$ORIGINAL_DIR" rm -rf "$TEST_DIR" + TEST_DIR="" } run_script() { + local tmpdir="${TMPDIR:-/tmp}" set +e - bash "$SCRIPT" >"$TMPDIR/crt-stdout" 2>"$TMPDIR/crt-stderr" + bash "$SCRIPT" >"$tmpdir/crt-stdout" 2>"$tmpdir/crt-stderr" LAST_EXIT=$? set -e - LAST_STDOUT=$(cat "$TMPDIR/crt-stdout") - LAST_STDERR=$(cat "$TMPDIR/crt-stderr") + LAST_STDOUT=$(cat "$tmpdir/crt-stdout") + LAST_STDERR=$(cat "$tmpdir/crt-stderr") } assert_eq() { From f3bbbf7dfd8cf3e358646d4ab6b607978b1d3f9c Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 3 May 2026 16:05:54 -0700 Subject: [PATCH 06/10] Remove unused base_tag output --- .github/scripts/compute-release-tag.sh | 2 -- .github/scripts/compute-release-tag.test.sh | 3 --- 2 files changed, 5 deletions(-) diff --git a/.github/scripts/compute-release-tag.sh b/.github/scripts/compute-release-tag.sh index 648b32cbd..640800a7e 100755 --- a/.github/scripts/compute-release-tag.sh +++ b/.github/scripts/compute-release-tag.sh @@ -5,7 +5,6 @@ set -euo pipefail # # Stdout (KEY=VALUE lines when a new tag should be created): # tag=vX.Y.Z -# base_tag=vX.Y # # Exit codes: # 0 - success (empty stdout means nothing to release) @@ -38,4 +37,3 @@ if git rev-parse "$TAG" >/dev/null 2>&1; then fi echo "tag=$TAG" -echo "base_tag=$LATEST_TAG" diff --git a/.github/scripts/compute-release-tag.test.sh b/.github/scripts/compute-release-tag.test.sh index 17f211bc4..e8b4d425a 100755 --- a/.github/scripts/compute-release-tag.test.sh +++ b/.github/scripts/compute-release-tag.test.sh @@ -92,7 +92,6 @@ git commit --allow-empty -m "feat 2" -q run_script assert_eq "$LAST_EXIT" "0" "exits 0" assert_contains "$LAST_STDOUT" "tag=v1.0.2" "computes v1.0.2" -assert_contains "$LAST_STDOUT" "base_tag=v1.0" "reports base tag" teardown_repo # ── Single commit ───────────────────────────────────── @@ -137,7 +136,6 @@ git commit --allow-empty -m "b" -q run_script assert_eq "$LAST_EXIT" "0" "exits 0" assert_contains "$LAST_STDOUT" "tag=v1.1.1" "uses latest base for version" -assert_contains "$LAST_STDOUT" "base_tag=v1.1" "reports v1.1 as base" teardown_repo # ── Ignores non-base patch tags ────────────────────── @@ -151,7 +149,6 @@ git commit --allow-empty -m "c" -q run_script assert_eq "$LAST_EXIT" "0" "exits 0" assert_contains "$LAST_STDOUT" "tag=v1.0.3" "counts all commits since base, not since patch" -assert_contains "$LAST_STDOUT" "base_tag=v1.0" "base is v1.0 not v1.0.1" teardown_repo echo "" From 93584cb3662bf62c7508ef407402f9d60f37db5a Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 3 May 2026 18:08:27 -0700 Subject: [PATCH 07/10] Delete compute-release-tag.test.sh --- .github/scripts/compute-release-tag.test.sh | 156 -------------------- 1 file changed, 156 deletions(-) delete mode 100755 .github/scripts/compute-release-tag.test.sh diff --git a/.github/scripts/compute-release-tag.test.sh b/.github/scripts/compute-release-tag.test.sh deleted file mode 100755 index e8b4d425a..000000000 --- a/.github/scripts/compute-release-tag.test.sh +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -SCRIPT="$SCRIPT_DIR/compute-release-tag.sh" -ORIGINAL_DIR="$PWD" -PASS=0 -FAIL=0 -TEST_DIR="" - -trap 'cd "$ORIGINAL_DIR"; [ -n "$TEST_DIR" ] && rm -rf "$TEST_DIR"' EXIT - -setup_repo() { - TEST_DIR=$(mktemp -d) - cd "$TEST_DIR" - git init -q - git config user.email "test@test.com" - git config user.name "Test" - git commit --allow-empty -m "init" -q -} - -teardown_repo() { - cd "$ORIGINAL_DIR" - rm -rf "$TEST_DIR" - TEST_DIR="" -} - -run_script() { - local tmpdir="${TMPDIR:-/tmp}" - set +e - bash "$SCRIPT" >"$tmpdir/crt-stdout" 2>"$tmpdir/crt-stderr" - LAST_EXIT=$? - set -e - LAST_STDOUT=$(cat "$tmpdir/crt-stdout") - LAST_STDERR=$(cat "$tmpdir/crt-stderr") -} - -assert_eq() { - local actual="$1" expected="$2" name="$3" - if [ "$actual" = "$expected" ]; then - echo " PASS: $name" - PASS=$((PASS + 1)) - else - echo " FAIL: $name" - echo " expected: $expected" - echo " actual: $actual" - FAIL=$((FAIL + 1)) - fi -} - -assert_contains() { - local haystack="$1" needle="$2" name="$3" - if echo "$haystack" | grep -qF "$needle"; then - echo " PASS: $name" - PASS=$((PASS + 1)) - else - echo " FAIL: $name" - echo " expected to contain: $needle" - echo " got: $haystack" - FAIL=$((FAIL + 1)) - fi -} - -echo "compute-release-tag.sh" -echo "" - -# ── No base tag ─────────────────────────────────────── -echo "no base tag:" -setup_repo -run_script -assert_eq "$LAST_EXIT" "1" "exits 1" -assert_contains "$LAST_STDERR" "No version tag found" "prints error to stderr" -assert_eq "$LAST_STDOUT" "" "no stdout" -teardown_repo - -# ── No commits since tag ────────────────────────────── -echo "no commits since tag:" -setup_repo -git tag v1.0 -run_script -assert_eq "$LAST_EXIT" "0" "exits 0" -assert_eq "$LAST_STDOUT" "" "no stdout" -assert_contains "$LAST_STDERR" "Nothing to release" "prints skip message" -teardown_repo - -# ── Normal patch bump ───────────────────────────────── -echo "normal patch bump (2 commits):" -setup_repo -git tag v1.0 -git commit --allow-empty -m "feat 1" -q -git commit --allow-empty -m "feat 2" -q -run_script -assert_eq "$LAST_EXIT" "0" "exits 0" -assert_contains "$LAST_STDOUT" "tag=v1.0.2" "computes v1.0.2" -teardown_repo - -# ── Single commit ───────────────────────────────────── -echo "single commit:" -setup_repo -git tag v3.2 -git commit --allow-empty -m "fix" -q -run_script -assert_eq "$LAST_EXIT" "0" "exits 0" -assert_contains "$LAST_STDOUT" "tag=v3.2.1" "computes v3.2.1" -teardown_repo - -# ── Duplicate tag ───────────────────────────────────── -echo "duplicate tag already exists:" -setup_repo -git tag v1.0 -git commit --allow-empty -m "feat" -q -git tag v1.0.1 -run_script -assert_eq "$LAST_EXIT" "0" "exits 0" -assert_eq "$LAST_STDOUT" "" "no stdout" -assert_contains "$LAST_STDERR" "already exists" "prints skip message" -teardown_repo - -# ── vX.Y.0 base tag format ─────────────────────────── -echo "vX.Y.0 base tag format:" -setup_repo -git tag v2.5.0 -git commit --allow-empty -m "feat" -q -run_script -assert_eq "$LAST_EXIT" "0" "exits 0" -assert_contains "$LAST_STDOUT" "tag=v2.5.1" "computes v2.5.1 from v2.5.0 base" -teardown_repo - -# ── Picks latest base tag ──────────────────────────── -echo "picks latest base tag when multiple exist:" -setup_repo -git tag v1.0 -git commit --allow-empty -m "a" -q -git tag v1.1 -git commit --allow-empty -m "b" -q -run_script -assert_eq "$LAST_EXIT" "0" "exits 0" -assert_contains "$LAST_STDOUT" "tag=v1.1.1" "uses latest base for version" -teardown_repo - -# ── Ignores non-base patch tags ────────────────────── -echo "ignores existing patch tags as base:" -setup_repo -git tag v1.0 -git commit --allow-empty -m "a" -q -git tag v1.0.1 -git commit --allow-empty -m "b" -q -git commit --allow-empty -m "c" -q -run_script -assert_eq "$LAST_EXIT" "0" "exits 0" -assert_contains "$LAST_STDOUT" "tag=v1.0.3" "counts all commits since base, not since patch" -teardown_repo - -echo "" -echo "Results: ${PASS} passed, ${FAIL} failed" -[ "$FAIL" -eq 0 ] || exit 1 From 4dae2da00a5a1789d64c9cf63fc44ebd9d7baac4 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 3 May 2026 18:14:51 -0700 Subject: [PATCH 08/10] Inline release tag script and add quiet period sleep --- .github/scripts/compute-release-tag.sh | 39 -------------------------- .github/workflows/code-tag.yml | 28 +++++++++++++++--- 2 files changed, 24 insertions(+), 43 deletions(-) delete mode 100755 .github/scripts/compute-release-tag.sh diff --git a/.github/scripts/compute-release-tag.sh b/.github/scripts/compute-release-tag.sh deleted file mode 100755 index 640800a7e..000000000 --- a/.github/scripts/compute-release-tag.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Computes the next patch release tag based on commits since the latest base tag. -# -# Stdout (KEY=VALUE lines when a new tag should be created): -# tag=vX.Y.Z -# -# Exit codes: -# 0 - success (empty stdout means nothing to release) -# 1 - no base version tag found - -LATEST_TAG=$(git tag --list 'v[0-9]*.[0-9]*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+(\.0)?$' | head -1 || true) - -if [ -z "$LATEST_TAG" ]; then - echo "No version tag found. Create one with: git tag v0.15 && git push origin v0.15" >&2 - exit 1 -fi - -VERSION_PART=${LATEST_TAG#v} -MAJOR=$(echo "$VERSION_PART" | cut -d. -f1) -MINOR=$(echo "$VERSION_PART" | cut -d. -f2) - -PATCH=$(git rev-list "$LATEST_TAG"..HEAD --count) - -if [ "$PATCH" -eq 0 ]; then - echo "No commits since $LATEST_TAG. Nothing to release." >&2 - exit 0 -fi - -NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" -TAG="v$NEW_VERSION" - -if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists. Nothing new to release." >&2 - exit 0 -fi - -echo "tag=$TAG" diff --git a/.github/workflows/code-tag.yml b/.github/workflows/code-tag.yml index 783ad70a4..7771a9c3d 100644 --- a/.github/workflows/code-tag.yml +++ b/.github/workflows/code-tag.yml @@ -67,7 +67,9 @@ jobs: fi NEXT=$((RETRIES + 1)) - echo "Last commit was ${AGE_MINUTES}m ago (< 15m). Re-dispatching retry ${NEXT}/${MAX_RETRIES}..." + WAIT_SECONDS=$(( (15 - AGE_MINUTES) * 60 )) + echo "Last commit was ${AGE_MINUTES}m ago (< 15m). Sleeping ${WAIT_SECONDS}s before retry ${NEXT}/${MAX_RETRIES}..." + sleep "$WAIT_SECONDS" gh workflow run code-tag.yml -f quiet_retries="$NEXT" echo "proceed=false" >> "$GITHUB_OUTPUT" @@ -77,13 +79,31 @@ jobs: GH_TOKEN: ${{ steps.app-token.outputs.token }} REPOSITORY: ${{ github.repository }} run: | - RESULT=$(.github/scripts/compute-release-tag.sh) + LATEST_TAG=$(git tag --list 'v[0-9]*.[0-9]*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+(\.0)?$' | head -1 || true) - if [ -z "$RESULT" ]; then + if [ -z "$LATEST_TAG" ]; then + echo "No version tag found. Create one with: git tag v0.15 && git push origin v0.15" + exit 1 + fi + + VERSION_PART=${LATEST_TAG#v} + MAJOR=$(echo "$VERSION_PART" | cut -d. -f1) + MINOR=$(echo "$VERSION_PART" | cut -d. -f2) + + PATCH=$(git rev-list "$LATEST_TAG"..HEAD --count) + + if [ "$PATCH" -eq 0 ]; then + echo "No commits since $LATEST_TAG. Nothing to release." + exit 0 + fi + + TAG="v${MAJOR}.${MINOR}.${PATCH}" + + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists. Nothing new to release." exit 0 fi - TAG=$(echo "$RESULT" | grep '^tag=' | cut -d= -f2) echo "Creating tag: $TAG" git config user.email "github-actions[bot]@users.noreply.github.com" From fe099df54c320de968b6086f536895d1f6a5f2ef Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 3 May 2026 18:18:46 -0700 Subject: [PATCH 09/10] Fix string comparison and restore verbose tag message --- .github/workflows/code-tag.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-tag.yml b/.github/workflows/code-tag.yml index 7771a9c3d..b178c17d4 100644 --- a/.github/workflows/code-tag.yml +++ b/.github/workflows/code-tag.yml @@ -43,7 +43,7 @@ jobs: - name: Check quiet period id: quiet - if: github.event_name == 'schedule' || github.event.inputs.quiet_retries > 0 + if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.quiet_retries != '0') env: GH_TOKEN: ${{ steps.app-token.outputs.token }} QUIET_RETRIES: ${{ github.event.inputs.quiet_retries || 0 }} @@ -104,7 +104,7 @@ jobs: exit 0 fi - echo "Creating tag: $TAG" + echo "Creating tag: $TAG (from base tag $LATEST_TAG + $PATCH commits)" git config user.email "github-actions[bot]@users.noreply.github.com" git config user.name "github-actions[bot]" From 7ba8c4592cc8832d5f9c461f58576882dcc8bece Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 3 May 2026 18:24:58 -0700 Subject: [PATCH 10/10] Update code-tag.yml --- .github/workflows/code-tag.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-tag.yml b/.github/workflows/code-tag.yml index b178c17d4..07d742658 100644 --- a/.github/workflows/code-tag.yml +++ b/.github/workflows/code-tag.yml @@ -49,7 +49,7 @@ jobs: QUIET_RETRIES: ${{ github.event.inputs.quiet_retries || 0 }} run: | RETRIES="${QUIET_RETRIES}" - MAX_RETRIES=3 + MAX_RETRIES=5 LAST_COMMIT_EPOCH=$(git log -1 --format=%ct HEAD) NOW_EPOCH=$(date +%s) AGE_MINUTES=$(( (NOW_EPOCH - LAST_COMMIT_EPOCH) / 60 ))