ci(release): only file issues on failure and reconcile stale ones (#15852)

* ci(release): file failure issues only for failures and close stale ones

The VSCode companion and Chrome extension notifiers filed an issue when a job
was merely cancelled, and nothing closed that issue once a later attempt
shipped, leaving a live failure report against a released version.

Drop the cancelled arm from both notifiers and add a success-gated
reconcile-failure job to each, reusing the shared release step. That step now
takes the title prefix and release version so all three release workflows can
close the bot-authored issue an earlier attempt filed.

Fixes #15840

* test(release): pin the version boundary when closing a stale issue

A nightly sibling of the same release sorts first in the gh issue list result, so dropping the " on " suffix from the title guard silently closes another release's issue. The fixture now covers that ordering and the boundary mutant is killed.

* test(release): tie each reconcile prefix to the title its notifier files

The fan-out suites asserted `FAILURE_TITLE_PREFIX` against a hand-written
constant, so renaming the notifier's `--title` left both green while the
real reconcile would match no issue and the stale release-failure issue
would stay open against a shipped release (QwenLM/qwen-code#15852).

Read the prefix out of the notifier's own `--title` argument and assert it
starts with the prefix the reconcile job passes, instead of restating the
literal a third time.

* test(ci): pin the version token and separator in the release title contract

reconcile-failure reads a notifier title through startswith(prefix + version
+ " on "), but the suite only pinned the prefix. A notifier that dropped
the version or separated it with something other than " on " closed
nothing while both suites stayed green, which is a silent failure of the
stale-issue cleanup (#15840).

Derive the rest of the title from the notifier's own --title, accept the
version variable with or without its :-unknown default, and assert the
reconcile job reads the same variable the notifier writes.

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: 2SinceJuly <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-11-07 12:52:38 +00:00
1 parent 978d9a23c3
commit 4d61487419
4 files changed
+286 -12

No files matched your search

+13 -6
View File
@@ -429,17 +429,24 @@ BODY
# good (#15705). Bot authorship is a hard requirement: a human may have
# filed the same title to track the flakiness, and closing their issue
# would discard that follow-up.
#
# Shared with the VSCode companion and Chrome extension release workflows
# (#15840), which file their own issues: they pass FAILURE_TITLE_PREFIX and
# RELEASE_VERSION because their titles and version outputs differ from
# release.yml's.
release_version="${RELEASE_VERSION:-${RELEASE_TAG:-unknown}}"
title_prefix="${FAILURE_TITLE_PREFIX:-Release Failed for }"
existing_issue="$(
gh issue list --repo "${GH_REPO}" \
--state open \
--search "\"Release Failed for ${RELEASE_TAG}\" in:title" \
--search "\"${title_prefix}${release_version}\" in:title" \
--limit 30 \
--json number,url,labels,author,title \
| jq -c --arg tag "${RELEASE_TAG}" \
'[ .[] | select(.title | startswith("Release Failed for " + $tag + " on ")) | select(.author.login == "github-actions[bot]") ][0] // empty'
| jq -c --arg prefix "${title_prefix}" --arg version "${release_version}" \
'[ .[] | select(.title | startswith($prefix + $version + " on ")) | select(.author.login == "github-actions[bot]") ][0] // empty'
)"
if [[ -z "${existing_issue}" ]]; then
echo "No workflow-owned release-failure issue is open for ${RELEASE_TAG}; nothing to reconcile."
echo "No workflow-owned release-failure issue is open for ${release_version}; nothing to reconcile."
exit 0
fi
issue_number="$(jq -r '.number' <<< "${existing_issue}")"
@@ -448,10 +455,10 @@ BODY
cat > "${body_file}" <<BODY
The release completed successfully on a later attempt of this run, so the failure this issue reported is no longer current.
Release tag: ${RELEASE_TAG}
Release tag: ${release_version}
Run: ${DETAILS_URL} (attempt ${RUN_ATTEMPT:-unknown})
Closing as not planned. Reopen if the ${RELEASE_TAG} artifacts later prove incomplete.
Closing as not planned. Reopen if the ${release_version} artifacts later prove incomplete.
BODY
gh issue comment "${issue_number}" --repo "${GH_REPO}" --body-file "${body_file}" || {
echo "::warning::Failed to comment on ${issue_url}; closing regardless."
+43 -3
View File
@@ -319,9 +319,7 @@ jobs:
always() &&
(
needs.build.result == 'failure' ||
needs.build.result == 'cancelled' ||
needs.publish.result == 'failure' ||
needs.publish.result == 'cancelled'
needs.publish.result == 'failure'
)
}}
runs-on: 'ubuntu-latest'
@@ -340,3 +338,45 @@ jobs:
--repo "${GH_REPO}" \
--title "Chrome extension release failed for ${RELEASE_VERSION:-unknown} on $(date +'%Y-%m-%d')" \
--body "The Chrome Web Store release workflow failed. See the full run for details: ${DETAILS_URL}"
reconcile-failure:
name: 'Close Stale Failure Issue'
# Success-gated sibling of report-failure: a re-run that passes skips the
# notifier, which is why the issue an earlier attempt filed would otherwise
# stay open against a shipped release (#15840).
needs:
- 'build'
- 'publish'
if: |-
${{
always() &&
needs.build.result == 'success' &&
needs.publish.result == 'success'
}}
runs-on: 'ubuntu-latest'
timeout-minutes: 10
permissions:
contents: 'read'
issues: 'write'
steps:
- name: 'Checkout release workflow scripts'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
ref: '${{ github.workflow_sha }}'
fetch-depth: 1
persist-credentials: false
sparse-checkout: |-
/.github/scripts
sparse-checkout-cone-mode: false
path: '.release-workflow'
- name: 'Close the issue an earlier attempt filed'
continue-on-error: true
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
GH_REPO: '${{ github.repository }}'
RELEASE_VERSION: '${{ needs.build.outputs.release_version }}'
FAILURE_TITLE_PREFIX: 'Chrome extension release failed for '
DETAILS_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
RUN_ATTEMPT: '${{ github.run_attempt }}'
run: '.release-workflow/.github/scripts/run-release-step.sh reconcile-failure'
+45 -3
View File
@@ -422,9 +422,7 @@ jobs:
always() &&
(
needs.build.result == 'failure' ||
needs.build.result == 'cancelled' ||
needs.publish.result == 'failure' ||
needs.publish.result == 'cancelled'
needs.publish.result == 'failure'
)
}}
runs-on: 'ubuntu-latest'
@@ -443,3 +441,47 @@ jobs:
--repo "${GH_REPO}" \
--title "VSCode IDE Companion Release Failed for ${RELEASE_VERSION} on $(date +'%Y-%m-%d')" \
--body "The VSCode IDE Companion release workflow failed. See the full run for details: ${DETAILS_URL}"
reconcile-failure:
name: 'Close Stale Failure Issue'
# Success-gated sibling of report-failure: a re-run that passes skips the
# notifier, which is why the issue an earlier attempt filed would otherwise
# stay open against a shipped release (#15840).
needs:
- 'prepare'
- 'build'
- 'publish'
if: |-
${{
always() &&
needs.prepare.result == 'success' &&
needs.build.result == 'success' &&
needs.publish.result == 'success'
}}
runs-on: 'ubuntu-latest'
timeout-minutes: 10
permissions:
contents: 'read'
issues: 'write'
steps:
- name: 'Checkout release workflow scripts'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
ref: '${{ github.workflow_sha }}'
fetch-depth: 1
persist-credentials: false
sparse-checkout: |-
/.github/scripts
sparse-checkout-cone-mode: false
path: '.release-workflow'
- name: 'Close the issue an earlier attempt filed'
continue-on-error: true
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
GH_REPO: '${{ github.repository }}'
RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'
FAILURE_TITLE_PREFIX: 'VSCode IDE Companion Release Failed for '
DETAILS_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
RUN_ATTEMPT: '${{ github.run_attempt }}'
run: '.release-workflow/.github/scripts/run-release-step.sh reconcile-failure'