Skip to content

Commit 097e4ac

Browse files
committed
ci(showcase/capture-previews): alert oss-alerts on job failure
The workflow runs exclusively on main-branch pushes, completed "Showcase: Build & Deploy" runs, and manual dispatch — all production events where silent failures (e.g. the GH013 PROTECT_OUR_MAIN regression that motivated PR CopilotKit#4159) must surface in #oss-alerts rather than getting buried in the Actions tab. Mirrors the failure-alert pattern from showcase_validate.yml: - Hoist SLACK_WEBHOOK_OSS_ALERTS into a job-level env var so step-level `if:` expressions can reference it (secrets.* is not a valid named-value inside `if:`). - Best-effort "Extract failure details" step pulls the failed step name and first meaningful error line from the jobs API + `gh run view --log-failed`, truncated to 300 chars. - `slackapi/slack-github-action@v2.1.0` with toJSON(format(...)) wrapping to safely JSON-encode any dynamic values. - Fallback `::warning::` log when the webhook secret is unset so the gap is still visible in the workflow output. Gated on `failure() && env.SLACK_WEBHOOK != ''`. No `github.event_name` filter needed — this workflow has no pull_request trigger, so every failure is an actionable production event.
1 parent dff94fb commit 097e4ac

1 file changed

Lines changed: 105 additions & 0 deletions

File tree

.github/workflows/showcase_capture-previews.yml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ permissions:
3131
jobs:
3232
capture:
3333
name: Capture Preview MP4s
34+
# Hoist the Slack webhook into an env var so step-level `if:`
35+
# expressions can reference it — `secrets.*` is not a valid
36+
# named-value inside `if:` and causes a workflow startup failure.
37+
env:
38+
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
3439
runs-on: ubuntu-latest
3540
# Captures demo previews as MP4 and uploads them to a GitHub release.
3641
# Loop prevention: capture commits registry.json with the message below,
@@ -138,3 +143,103 @@ jobs:
138143
git commit -m "Update preview URLs in registry"
139144
git push
140145
fi
146+
147+
# Slack failure alert. This workflow only runs on main-branch pushes,
148+
# workflow_run completions, and manual dispatch — all of which
149+
# constitute "production" events where silent failures (e.g. the
150+
# GH013 PROTECT_OUR_MAIN regression that motivated PR #4159) must
151+
# surface in #oss-alerts. Extracts failed step name + first
152+
# meaningful error line so the payload is triage-ready rather than
153+
# forcing a click-through, per the oss-alerts detail policy.
154+
#
155+
# Must NEVER fail the job (runs on failure() already; a crash here
156+
# would compound the original failure and could block the notify
157+
# step). All extraction uses best-effort fallbacks so a malformed
158+
# jobs response or truncated log still yields sane defaults.
159+
- name: Extract failure details for Slack
160+
id: extract
161+
if: failure() && env.SLACK_WEBHOOK != ''
162+
env:
163+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
164+
GH_REPO: ${{ github.repository }}
165+
RUN_ID: ${{ github.run_id }}
166+
run: |
167+
set +e # best-effort: never block the notify step below
168+
169+
# --- Find the currently-running job and its first failed step ---
170+
# The jobs API returns every job in the run. Match by job name
171+
# first (mirrors `jobs.capture.name`); fall back to first job
172+
# with a failed step if name match misses (e.g. future rename).
173+
jobs_json=$(gh api "/repos/${GH_REPO}/actions/runs/${RUN_ID}/jobs" --paginate 2>/dev/null)
174+
job_id=$(printf '%s' "$jobs_json" | jq -r '
175+
.jobs // []
176+
| map(select(.name == "Capture Preview MP4s"))
177+
| (.[0].id // empty)
178+
' 2>/dev/null)
179+
if [ -z "$job_id" ]; then
180+
job_id=$(printf '%s' "$jobs_json" | jq -r '
181+
.jobs // []
182+
| map(select(.steps // [] | map(.conclusion) | index("failure")))
183+
| (.[0].id // empty)
184+
' 2>/dev/null)
185+
fi
186+
failed_step=$(printf '%s' "$jobs_json" | jq -r --arg id "$job_id" '
187+
.jobs // []
188+
| map(select((.id|tostring) == $id))
189+
| (.[0].steps // [])
190+
| map(select(.conclusion == "failure"))
191+
| (.[0].name // "unknown step")
192+
' 2>/dev/null)
193+
[ -z "$failed_step" ] && failed_step="unknown step"
194+
195+
# --- Pull log and extract first meaningful error line ------------
196+
# `gh run view --log-failed` output is TSV: job\tstep\ttimestamp +
197+
# content. Strip the three leading columns, strip ANSI escape
198+
# codes + BOM, skip runner/group/env header noise, grab first
199+
# line matching a recognised error marker. Truncate to ~300
200+
# chars so the Slack payload stays under the 800-char budget.
201+
error_excerpt="see workflow run for details"
202+
if [ -n "$job_id" ]; then
203+
log_excerpt=$(gh run view "$RUN_ID" --repo "$GH_REPO" --log-failed --job="$job_id" 2>/dev/null \
204+
| awk -F'\t' 'NF>=3 { sub(/^[\xEF\xBB\xBF]?[0-9T:.\-Z ]+/, "", $3); print $3 }' \
205+
| sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' \
206+
| grep -vE '^(##\[|shell: |env: |Run |[[:space:]]*$)' \
207+
| grep -m1 -E '^\[(FAIL|ERROR)\]|^Error:|^error:|^::error' \
208+
| head -c 300)
209+
if [ -n "$log_excerpt" ]; then
210+
error_excerpt="$log_excerpt"
211+
fi
212+
fi
213+
214+
# --- Emit to $GITHUB_ENV using heredoc delimiter -----------------
215+
# Heredoc delimiter protects against values with `=` or newlines
216+
# breaking the KEY=VALUE format.
217+
{
218+
echo "failed_step<<EOF_FAILED_STEP_b3f2"
219+
printf '%s\n' "$failed_step"
220+
echo "EOF_FAILED_STEP_b3f2"
221+
echo "error_excerpt<<EOF_ERROR_EXCERPT_b3f2"
222+
printf '%s\n' "$error_excerpt"
223+
echo "EOF_ERROR_EXCERPT_b3f2"
224+
} >> "$GITHUB_ENV"
225+
226+
exit 0 # belt-and-suspenders: never propagate a failure
227+
228+
- name: Notify Slack (failure)
229+
if: failure() && env.SLACK_WEBHOOK != ''
230+
uses: slackapi/slack-github-action@v2.1.0
231+
with:
232+
webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
233+
webhook-type: incoming-webhook
234+
# Defensive: wrap dynamic values via toJSON(format(...)) so that
235+
# if github.repository or extracted failed_step / error_excerpt
236+
# contain characters that would break the JSON payload (quotes,
237+
# backslashes, newlines), the value is safely JSON-encoded.
238+
# Matches the pattern used in showcase_validate.yml.
239+
payload: |
240+
{ "text": ${{ toJSON(format(':x: *Showcase: Capture Previews*: failed — {0}: {1} | <https://github.com/{2}/actions/runs/{3}|View run>', env.failed_step, env.error_excerpt, github.repository, github.run_id)) }} }
241+
242+
- name: Log (no Slack — webhook unset)
243+
if: failure() && env.SLACK_WEBHOOK == ''
244+
run: |
245+
echo "::warning::showcase_capture-previews failed but SLACK_WEBHOOK_OSS_ALERTS is not set; no Slack notification sent."

0 commit comments

Comments
 (0)