qwen-code/.github/workflows/finalize-release.yml
yiliang114 33414ab1b0 fix(release): raise model timeouts and shrink batch size for slow networks
The AI release-notes generator timed out on every batch in the v0.21.1
finalize run on a GitHub-hosted runner (US, Azure westus3) hitting a
remote LLM endpoint. The 60s per-request timeout was too close to the
edge: two successful requests took 54.2s and 56.7s, and every other
batch hit the 60s abort. The 12-minute total budget was consumed by
retries on those timeouts, and the circuit breaker opened after 3
consecutive failures, skipping highlights entirely.

- timeoutMs: 60s -> 180s — give the model enough headroom to generate
  a full JSON response over a high-RTT cross-region connection.
- totalTimeoutMs: 12min -> 30min — ~140 PRs at 8 per batch is ~18
  batches; at ~90s each that's ~27 minutes of model time.
- batchSize: 12 -> 8 — fewer entries per prompt means the model
  generates less per request, returning faster and reducing the
  chance of a single slow request dragging the whole batch.
- workflow timeout-minutes: 15 -> 35 — match the new 30min budget
  plus a margin for git/npm setup.

No changes to retry logic, circuit breaker, or prompt shape.
2026-07-29 14:09:43 +08:00

258 lines
10 KiB
YAML

name: 'Finalize Stable Release'
on:
release:
types: ['published']
workflow_dispatch:
inputs:
tag:
description: 'The stable release tag to finalize (e.g., v0.19.10).'
required: true
type: 'string'
concurrency:
group: 'finalize-stable-release-${{ github.event.release.tag_name || inputs.tag }}'
cancel-in-progress: false
jobs:
finalize:
name: 'Finalize Stable Release'
runs-on: 'ubuntu-latest'
if: |-
${{ github.repository == 'QwenLM/qwen-code' && (github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false) }}
environment:
name: 'production-release'
permissions:
contents: 'read'
env:
RELEASE_TAG: '${{ github.event.release.tag_name || inputs.tag }}'
steps:
- name: 'Validate stable release tag'
id: 'meta'
env:
TAG: '${{ env.RELEASE_TAG }}'
run: |-
is_stable="false"
if [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
is_stable="true"
fi
echo "is_stable=${is_stable}" >> "${GITHUB_OUTPUT}"
echo "release_branch=release/${TAG}" >> "${GITHUB_OUTPUT}"
if [[ "${is_stable}" != "true" ]]; then
echo "::error::${TAG} is not a stable release tag; finalize handles stable tags only."
exit 1
fi
- name: 'Checkout release branch'
if: |-
${{ steps.meta.outputs.is_stable == 'true' }}
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
token: '${{ secrets.CI_BOT_PAT }}'
ref: '${{ steps.meta.outputs.release_branch }}'
fetch-depth: 0
- name: 'Setup Node.js'
if: |-
${{ steps.meta.outputs.is_stable == 'true' }}
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
- name: 'Install Dependencies'
if: |-
${{ steps.meta.outputs.is_stable == 'true' }}
env:
NPM_CONFIG_PREFER_OFFLINE: 'true'
QWEN_SKIP_PREPARE: '1'
run: |-
npm ci --no-audit --progress=false
- name: 'Configure Git User'
if: |-
${{ steps.meta.outputs.is_stable == 'true' }}
run: |-
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git config core.hooksPath .husky
- name: 'Resolve previous stable release tag'
if: |-
${{ steps.meta.outputs.is_stable == 'true' }}
id: 'previous'
env:
TAG: '${{ env.RELEASE_TAG }}'
run: |-
previous_tag=""
found_current="false"
while IFS= read -r stable_tag; do
if [[ "${found_current}" == "true" ]]; then
previous_tag="${stable_tag}"
break
fi
if [[ "${stable_tag}" == "${TAG}" ]]; then
found_current="true"
fi
done < <(git tag --list 'v*' --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$')
if [[ -z "${previous_tag}" ]]; then
echo "::error::Could not resolve the stable release before ${TAG}."
exit 1
fi
echo "tag=${previous_tag}" >> "${GITHUB_OUTPUT}"
- name: 'Generate AI-assisted release notes'
if: |-
${{ steps.meta.outputs.is_stable == 'true' }}
continue-on-error: true
timeout-minutes: 35
env:
GITHUB_TOKEN: '${{ secrets.CI_BOT_PAT }}'
OPENAI_API_KEY: '${{ secrets.OPENAI_API_KEY }}'
OPENAI_BASE_URL: '${{ secrets.OPENAI_BASE_URL }}'
OPENAI_MODEL: '${{ secrets.OPENAI_MODEL }}'
PREVIOUS_RELEASE_TAG: '${{ steps.previous.outputs.tag }}'
RELEASE_BRANCH: '${{ steps.meta.outputs.release_branch }}'
RELEASE_NOTES_FILE: '${{ runner.temp }}/release-notes.md'
RELEASE_TAG: '${{ env.RELEASE_TAG }}'
run: |-
# The first publish keeps GitHub-generated notes; this improves them after the tag exists.
node scripts/generate-release-notes.js \
--repo="${GITHUB_REPOSITORY}" \
--tag="${RELEASE_TAG}" \
--previous-tag="${PREVIOUS_RELEASE_TAG}" \
--target="${RELEASE_BRANCH}" \
--output="${RELEASE_NOTES_FILE}"
- name: 'Update GitHub Release notes'
if: |-
${{ steps.meta.outputs.is_stable == 'true' }}
continue-on-error: true
env:
GITHUB_TOKEN: '${{ secrets.CI_BOT_PAT }}'
RELEASE_NOTES_FILE: '${{ runner.temp }}/release-notes.md'
RELEASE_TAG: '${{ env.RELEASE_TAG }}'
run: |-
if [[ -s "${RELEASE_NOTES_FILE}" ]]; then
gh release edit "${RELEASE_TAG}" --notes-file "${RELEASE_NOTES_FILE}"
else
echo "::warning::AI release notes were not generated; keeping GitHub-generated notes."
fi
- name: 'Comment released-in version on merged PRs'
if: |-
${{ steps.meta.outputs.is_stable == 'true' }}
continue-on-error: true
env:
GITHUB_TOKEN: '${{ secrets.CI_BOT_PAT }}'
PREVIOUS_TAG: '${{ steps.previous.outputs.tag }}'
RELEASE_TAG: '${{ env.RELEASE_TAG }}'
run: |-
set -euo pipefail
# Squash-merge commit subjects carry the PR as "(#NNN)"; collect those between the two tags.
# The trailing "|| true" keeps "no matches" (empty range) from failing under pipefail.
pr_numbers="$(git log --pretty=format:'%s' "${PREVIOUS_TAG}..${RELEASE_TAG}" | grep -oE '\(#[0-9]+\)$' | tr -d '()#' | sort -un || true)"
if [[ -z "${pr_numbers}" ]]; then
echo "No PR references found between ${PREVIOUS_TAG} and ${RELEASE_TAG}."
exit 0
fi
echo "Found PRs between ${PREVIOUS_TAG} and ${RELEASE_TAG}:"
echo "${pr_numbers}"
marker='<!-- qwen-release-comment:v1 -->'
release_url="https://github.com/${GITHUB_REPOSITORY}/releases/tag/${RELEASE_TAG}"
while IFS= read -r num; do
if [[ -z "${num}" ]]; then
continue
fi
# Re-run safety: skip PRs that already carry this release marker.
existing="$(gh pr view "${num}" --json comments --jq '.comments[].body' 2>/dev/null || true)"
if grep -qF "${marker}" <<<"${existing}"; then
echo "PR #${num} already has a release comment; skipping."
continue
fi
body="${marker}"$'\n'"Released in [${RELEASE_TAG}](${release_url})."
gh pr comment "${num}" --body "${body}" && echo "Commented on PR #${num}." || echo "::warning::Failed to comment on PR #${num}."
done <<< "${pr_numbers}"
- name: 'Regenerate CHANGELOG.md'
if: |-
${{ steps.meta.outputs.is_stable == 'true' }}
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
BRANCH_NAME: '${{ steps.meta.outputs.release_branch }}'
RELEASE_TAG: '${{ env.RELEASE_TAG }}'
run: |-
set -euo pipefail
node scripts/generate-changelog.js
git add CHANGELOG.md
if git diff --cached --quiet -- CHANGELOG.md; then
echo "CHANGELOG.md already up to date."
else
git commit -m "docs(changelog): sync for ${RELEASE_TAG}"
git push origin "${BRANCH_NAME}"
fi
- name: 'Create PR to merge release branch into main'
if: |-
${{ steps.meta.outputs.is_stable == 'true' }}
id: 'pr'
env:
GITHUB_TOKEN: '${{ secrets.CI_BOT_PAT }}'
RELEASE_BRANCH: '${{ steps.meta.outputs.release_branch }}'
RELEASE_TAG: '${{ env.RELEASE_TAG }}'
run: |-
set -euo pipefail
# Merge the release branch back to main so package metadata and CHANGELOG converge.
pr_data="$(gh pr list --head "${RELEASE_BRANCH}" --base main --state all --json url,state --jq '(map(select(.state == "MERGED"))[0] // map(select(.state == "OPEN"))[0]) // empty | [.url, .state] | @tsv')"
pr_url=""
pr_state=""
if [[ -n "${pr_data}" ]]; then
IFS=$'\t' read -r pr_url pr_state <<< "${pr_data}"
fi
if [[ "${pr_state}" == "MERGED" ]]; then
echo "Release PR already merged; skipping approval and auto-merge."
echo "PR_URL=${pr_url}" >> "${GITHUB_OUTPUT}"
echo "SHOULD_MERGE=false" >> "${GITHUB_OUTPUT}"
exit 0
fi
if [[ "${pr_state}" != "OPEN" ]]; then
pr_url="$(gh pr create \
--base main \
--head "${RELEASE_BRANCH}" \
--label 'skip-changelog' \
--title "chore(release): ${RELEASE_TAG}" \
--body "Automated release PR for ${RELEASE_TAG}. Syncs package.json versions and CHANGELOG.md on main.")"
fi
echo "PR_URL=${pr_url}" >> "${GITHUB_OUTPUT}"
echo "SHOULD_MERGE=true" >> "${GITHUB_OUTPUT}"
- name: 'Approve release PR'
if: |-
${{ steps.meta.outputs.is_stable == 'true' && steps.pr.outputs.SHOULD_MERGE == 'true' }}
env:
GITHUB_TOKEN: '${{ secrets.CI_DEV_BOT_PAT }}'
PR_URL: '${{ steps.pr.outputs.PR_URL }}'
run: |-
set -euo pipefail
gh pr review "${PR_URL}" --approve --body "Automated approval for the release version bump."
- name: 'Enable auto-merge for release PR'
if: |-
${{ steps.meta.outputs.is_stable == 'true' && steps.pr.outputs.SHOULD_MERGE == 'true' }}
env:
GITHUB_TOKEN: '${{ secrets.CI_BOT_PAT }}'
PR_URL: '${{ steps.pr.outputs.PR_URL }}'
RELEASE_TAG: '${{ env.RELEASE_TAG }}'
run: |-
set -euo pipefail
gh pr merge "${PR_URL}" \
--squash \
--auto \
--subject "chore(release): ${RELEASE_TAG} [skip ci]"