mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-10 09:15:24 +00:00
* 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. * fix(release): sync timeout workflow test * test(release): assert step timeout exceeds script budget The workflow `timeout-minutes` and the script's `totalTimeoutMs` are defined in separate files with no shared constant. If they drift apart silently and the budget exceeds the step timeout, the runner SIGKILLs the step and even the fallback release notes are lost. Add a cross-file assertion so any future change to either value that breaks the invariant fails the test immediately. Suggested-by: wenshao in PR review. * test(release): parse release notes timeout expression
258 lines
10 KiB
YAML
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]"
|