From 7815aee7e7f4508207631d9ff7f8ca40af412b8f Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 1 Jul 2026 15:00:16 -0500 Subject: [PATCH 1/3] auto open issue on ci failure during release Signed-off-by: Cassandra Coyle --- .github/workflows/open-issue-on-failure.yml | 150 ++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 .github/workflows/open-issue-on-failure.yml diff --git a/.github/workflows/open-issue-on-failure.yml b/.github/workflows/open-issue-on-failure.yml new file mode 100644 index 0000000..ed83179 --- /dev/null +++ b/.github/workflows/open-issue-on-failure.yml @@ -0,0 +1,150 @@ +# +# Copyright 2026 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Reusable workflow that opens (or comments on) a tracking issue when a +# release/publish job fails. Call it from a job gated with `if: failure()` +# in the workflow that runs the release. The issue is opened in the calling +# repository (context.repo). To avoid duplicates, if an open issue with the +# same title already exists, a comment is added to it instead of opening a +# new one. + +name: Open issue on failure + +on: + workflow_call: + inputs: + title: + required: true + type: string + description: Title of the issue to open. + body: + required: false + type: string + default: '' + description: Extra context appended to the generated issue body. + labels: + required: false + type: string + default: '' + description: Comma-separated list of labels to apply to the issue. + mention: + required: false + type: string + default: '' + description: > + Space-separated @-handles or teams to mention in the issue body, + e.g. "@dapr/maintainers-dapr @dapr/approvers-dapr". + secrets: + dapr_bot_token: + required: false + description: > + Token used to author the issue. When omitted, the built-in + GITHUB_TOKEN is used (needs issues: write on the calling job). + +permissions: {} + +jobs: + open-issue: + name: Open issue on failure + runs-on: ubuntu-latest + permissions: + issues: write + actions: read # to list the run's failed jobs and link to them + steps: + - uses: actions/github-script@v7 + env: + TITLE: ${{ inputs.title }} + EXTRA_BODY: ${{ inputs.body }} + LABELS: ${{ inputs.labels }} + MENTION: ${{ inputs.mention }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + WORKFLOW: ${{ github.workflow }} + REF: ${{ github.ref_name }} + SHA: ${{ github.sha }} + with: + github-token: ${{ secrets.dapr_bot_token || github.token }} + script: | + const { TITLE, EXTRA_BODY, LABELS, MENTION, RUN_URL, WORKFLOW, REF, SHA } = process.env; + const { owner, repo } = context.repo; + const labels = LABELS.split(',').map(l => l.trim()).filter(Boolean); + // Stable marker so we can re-find this issue even if the title changes. + const marker = ``; + + // Reusable workflows share the caller's run_id, so this lists every + // job in the release run. Drives both the failed-job links and the + // "already retried" note. Empty on error (needs actions: read). + let failed = []; + try { + const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, { + owner, repo, run_id: context.runId, per_page: 100, + }); + failed = jobs.filter(j => ['failure', 'cancelled', 'timed_out'].includes(j.conclusion)); + } catch (e) { + core.warning(`Could not list failed jobs (needs actions: read): ${e.message}`); + } + const failedJobs = failed.length + ? ['', '**Failed jobs:**', ...failed.map(j => `- [${j.name}](${j.html_url})`)].join('\n') + : ''; + + const runContext = [ + `- **Workflow run:** ${RUN_URL}`, + `- **Ref:** \`${REF}\``, + `- **Commit:** \`${SHA}\``, + ].join('\n'); + + const issueBody = [ + MENTION.trim(), + `A release/publish job failed in **${WORKFLOW}**.`, + '', + runContext, + failedJobs, + EXTRA_BODY.trim() && `\n${EXTRA_BODY.trim()}`, + '', + marker, + ].filter(Boolean).join('\n'); + + // Dedup: find an existing open issue with the same title (or marker). + const open = await github.paginate(github.rest.issues.listForRepo, { + owner, + repo, + state: 'open', + labels: labels.join(',') || undefined, + per_page: 100, + }); + const match = open.find(i => + !i.pull_request && (i.title === TITLE || (i.body || '').includes(marker))); + + if (match) { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: match.number, + body: [ + 'The release/publish job failed again.', + '', + runContext, + failedJobs, + ].filter(Boolean).join('\n'), + }); + core.notice(`Commented on existing issue #${match.number}`); + return; + } + + const created = await github.rest.issues.create({ + owner, + repo, + title: TITLE, + body: issueBody, + labels, + }); + core.notice(`Opened issue #${created.data.number}`); From 9cf3a05dc1b2fc2e3e9bb68e5b3dd32031944c91 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Thu, 2 Jul 2026 09:10:14 -0500 Subject: [PATCH 2/3] PR feedback Signed-off-by: Cassandra Coyle --- .github/workflows/open-issue-on-failure.yml | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/open-issue-on-failure.yml b/.github/workflows/open-issue-on-failure.yml index ed83179..add550e 100644 --- a/.github/workflows/open-issue-on-failure.yml +++ b/.github/workflows/open-issue-on-failure.yml @@ -77,12 +77,13 @@ jobs: const { TITLE, EXTRA_BODY, LABELS, MENTION, RUN_URL, WORKFLOW, REF, SHA } = process.env; const { owner, repo } = context.repo; const labels = LABELS.split(',').map(l => l.trim()).filter(Boolean); - // Stable marker so we can re-find this issue even if the title changes. - const marker = ``; + // Marker keyed to workflow + ref (not the title text), so re-runs of + // the same release re-find the issue even if the title wording changes. + const marker = ``; // Reusable workflows share the caller's run_id, so this lists every - // job in the release run. Drives both the failed-job links and the - // "already retried" note. Empty on error (needs actions: read). + // job in the release run. Used to build the failed-job links. + // Empty on error (needs actions: read). let failed = []; try { const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, { @@ -93,7 +94,7 @@ jobs: core.warning(`Could not list failed jobs (needs actions: read): ${e.message}`); } const failedJobs = failed.length - ? ['', '**Failed jobs:**', ...failed.map(j => `- [${j.name}](${j.html_url})`)].join('\n') + ? ['**Failed jobs:**', ...failed.map(j => `- [${j.name}](${j.html_url})`)].join('\n') : ''; const runContext = [ @@ -102,23 +103,23 @@ jobs: `- **Commit:** \`${SHA}\``, ].join('\n'); + // Each entry is a section; blank lines between them come from the + // \n\n join, and empty sections drop out. const issueBody = [ MENTION.trim(), `A release/publish job failed in **${WORKFLOW}**.`, - '', runContext, failedJobs, - EXTRA_BODY.trim() && `\n${EXTRA_BODY.trim()}`, - '', + EXTRA_BODY.trim(), marker, - ].filter(Boolean).join('\n'); + ].filter(Boolean).join('\n\n'); - // Dedup: find an existing open issue with the same title (or marker). + // Dedup: scan all open issues (not filtered by label, so a relabelled + // issue still matches) for the same marker or title. const open = await github.paginate(github.rest.issues.listForRepo, { owner, repo, state: 'open', - labels: labels.join(',') || undefined, per_page: 100, }); const match = open.find(i => @@ -131,10 +132,9 @@ jobs: issue_number: match.number, body: [ 'The release/publish job failed again.', - '', runContext, failedJobs, - ].filter(Boolean).join('\n'), + ].filter(Boolean).join('\n\n'), }); core.notice(`Commented on existing issue #${match.number}`); return; From 674b2a9d553d2ef9a15abb233121a5e0ccc2f8d8 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Thu, 2 Jul 2026 09:56:27 -0500 Subject: [PATCH 3/3] PR feedback Signed-off-by: Cassandra Coyle --- .github/workflows/open-issue-on-failure.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/open-issue-on-failure.yml b/.github/workflows/open-issue-on-failure.yml index add550e..e67d759 100644 --- a/.github/workflows/open-issue-on-failure.yml +++ b/.github/workflows/open-issue-on-failure.yml @@ -77,9 +77,10 @@ jobs: const { TITLE, EXTRA_BODY, LABELS, MENTION, RUN_URL, WORKFLOW, REF, SHA } = process.env; const { owner, repo } = context.repo; const labels = LABELS.split(',').map(l => l.trim()).filter(Boolean); - // Marker keyed to workflow + ref (not the title text), so re-runs of - // the same release re-find the issue even if the title wording changes. - const marker = ``; + // Hidden marker so re-runs re-find this issue even if a maintainer + // manually renames it. TITLE already encodes the workflow + release + // tag (callers resolve the tag), giving one issue per release. + const marker = ``; // Reusable workflows share the caller's run_id, so this lists every // job in the release run. Used to build the failed-job links.