From 75bd0582aeb2b6dd2c3f224bbbe9bf950ca37d94 Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Thu, 22 Oct 2026 10:11:22 +0200 Subject: [PATCH] T-22072 [CI] Keep the E2E Linear issue short and free of tables A failed job's summary goes into the issue in place of its errors, which only fill in for a job that stopped before writing one, once each. Summaries list their numbers as bullets, since Linear renders wide tables poorly. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/e2e.yml | 39 ++++++++++----------- .github/workflows/notify_linear_failure.yml | 20 ++++++----- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 8cc4fd9..7b33e5a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -281,13 +281,13 @@ jobs: echo "❌ ${FAILURE:-The read-back stopped on an error, see the log of this step}" if [ -n "${LOGS:-}" ]; then echo - echo "| read back through \`$CONNECT_HOST\` | arrived | expected |" - echo "|---|---|---|" - echo "| logs of \`$LOG_GROUP\` | $LOGS | 20 |" - echo "| logs of \`$STATIC_LOG_GROUP\` | $STATIC_LOGS | 20 |" - echo "| spans | $SPANS | 2 |" - echo "| metric points | $METRICS | 15 |" - echo "| CloudTrail event | $CLOUDTRAIL | 1 |" + echo "Read back through \`$CONNECT_HOST\`:" + echo + echo "- logs of \`$LOG_GROUP\`: $LOGS of 20" + echo "- logs of \`$STATIC_LOG_GROUP\`: $STATIC_LOGS of 20" + echo "- spans: $SPANS of 2" + echo "- metric points: $METRICS of 15" + echo "- CloudTrail event: $CLOUDTRAIL of 1" fi [ ! -f out/forwarding.md ] || { echo; cat out/forwarding.md; } } > out/summary.md @@ -321,21 +321,18 @@ jobs: # What CloudWatch Logs did with each group in the last 40 min; DeliveryErrors mean the filter paused for 10 min # and skipped what arrived meanwhile. forwarding() { - { - echo "| CloudWatch Logs, last 40 min | incoming | forwarded | delivery errors |" - echo "|---|---|---|---|" - } > out/forwarding.md + printf 'CloudWatch Logs in the last 40 min:\n\n' > out/forwarding.md for group in "$LOG_GROUP" "$STATIC_LOG_GROUP" aws/spans /aws/cloudtrail/better-stack; do - ROW="| \`$group\` |" + VALUES=() for metric in IncomingLogEvents ForwardedLogEvents DeliveryErrors; do DIMS="Name=LogGroupName,Value=$group" [ "$metric" = IncomingLogEvents ] || DIMS="$DIMS Name=DestinationType,Value=Firehose Name=FilterName,Value=BetterStackLogs" VALUE=$(aws cloudwatch get-metric-statistics --namespace AWS/Logs --metric-name "$metric" --dimensions $DIMS \ --start-time "$(date -u -d '-40 minutes' +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" --period 2400 --statistics Sum --query 'sum(Datapoints[].Sum)' --output text) echo "$group $metric $VALUE" - ROW="$ROW ${VALUE%.0} |" + VALUES+=("${VALUE%.0}") done - echo "$ROW" >> out/forwarding.md + echo "- \`$group\`: ${VALUES[0]} incoming, ${VALUES[1]} forwarded, ${VALUES[2]} delivery errors" >> out/forwarding.md done } # Within an hour of the previous stack deletion the static group, aws/spans and CloudTrail may deliver nothing @@ -415,18 +412,19 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - mkdir -p report if [ -f out/summary.md ]; then - cp out/summary.md report/summary.md + cat out/summary.md >> "$GITHUB_STEP_SUMMARY" + # The Linear issue names the job and links the run, so it gets the summary without its heading, and the + # cleanup only when that left something behind. Without a summary it gets the job's errors instead. + mkdir -p report + tail -n +3 out/summary.md > report/summary.md + grep -q '❌' out/cleanup.md 2>/dev/null && { echo; cat out/cleanup.md; } >> report/summary.md else # The read-back writes the summary however it ends, so a step before it failed (or the job was cancelled). FAILED_STEP=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/attempts/$GITHUB_RUN_ATTEMPT/jobs" \ --jq ".jobs[] | select(.runner_name == \"$RUNNER_NAME\") | .steps[] | select(.conclusion == \"failure\") | .name" | head -1) || true - { echo "### AWS CloudFormation stack E2E \`$RUN_ID\`"; echo; echo "❌ Stopped${FAILED_STEP:+ at **$FAILED_STEP**}, before anything was read back."; } > report/summary.md + { echo "### AWS CloudFormation stack E2E \`$RUN_ID\`"; echo; echo "❌ Stopped${FAILED_STEP:+ at **$FAILED_STEP**}, before anything was read back."; } >> "$GITHUB_STEP_SUMMARY" fi - cat report/summary.md >> "$GITHUB_STEP_SUMMARY" - # The Linear issue gets the cleanup only when it left something behind. - grep -q '❌' out/cleanup.md 2>/dev/null && { echo; cat out/cleanup.md; } >> report/summary.md # Rows come back as JSONEachRow with the stored event as a JSON string in raw; render them as a pretty array. for f in out/sample_*.json; do [ -f "$f" ] && { echo; echo "
$(basename "$f")"; echo; echo '```json'; jq -s 'map(if has("raw") then .raw |= (fromjson? // .) else . end)' "$f"; echo '```'; echo '
'; echo; } >> "$GITHUB_STEP_SUMMARY"; done [ -f out/cleanup.md ] && { echo; cat out/cleanup.md; } >> "$GITHUB_STEP_SUMMARY" @@ -438,6 +436,7 @@ jobs: with: name: e2e-summary-Stack round trip path: report/summary.md + if-no-files-found: ignore retention-days: 7 overwrite: true diff --git a/.github/workflows/notify_linear_failure.yml b/.github/workflows/notify_linear_failure.yml index 72f9f2d..4d0e962 100644 --- a/.github/workflows/notify_linear_failure.yml +++ b/.github/workflows/notify_linear_failure.yml @@ -10,8 +10,8 @@ on: default: "" job_summaries: description: >- - Adds each failed job's errors and summary to the issue. A job hands its summary over as an artifact named - "e2e-summary-" holding summary.md. + Adds each failed job's summary to the issue, or its errors when it has none. A job hands its summary over as + an artifact named "e2e-summary-" holding summary.md. type: boolean default: false @@ -49,14 +49,18 @@ jobs: FAILED="$FAILED- **$NAME** — failed at _${STEPS}_"$'\n' continue fi - # The job's ::error:: lines, without the runner's closing "Process completed with exit code 1." - ERRORS=$(gh api "repos/${{ github.repository }}/check-runs/$(jq -r .id <<<"$job")/annotations" \ - --jq '.[] | select(.annotation_level == "failure" and (.message | startswith("Process completed with exit code") | not)) | "- \(.message | gsub("\n"; " "))"') || ERRORS="" - FAILED="$FAILED### $NAME"$'\n\n'"Failed at **${STEPS}**"$'\n\n'"${ERRORS:+$ERRORS$'\n\n'}" - # A job that failed before writing its summary has no artifact and gets the errors alone. + FAILED="$FAILED### $NAME"$'\n\n'"Failed at **${STEPS}**"$'\n\n' + # A job's summary already says why it failed; a job without one (it stopped before writing it) gets its + # ::error:: lines instead, without the runner's closing "Process completed with exit code 1." SUMMARY_DIR="summaries/$(jq -r .id <<<"$job")" gh run download "$RUN_ID" -R "${{ github.repository }}" -n "e2e-summary-$NAME" -D "$SUMMARY_DIR" >/dev/null 2>&1 || true - [ ! -f "$SUMMARY_DIR/summary.md" ] || FAILED="$FAILED$(cat "$SUMMARY_DIR/summary.md")"$'\n\n' + if [ -f "$SUMMARY_DIR/summary.md" ]; then + FAILED="$FAILED$(cat "$SUMMARY_DIR/summary.md")"$'\n\n' + else + ERRORS=$(gh api "repos/${{ github.repository }}/check-runs/$(jq -r .id <<<"$job")/annotations" \ + --jq '[.[] | select(.annotation_level == "failure" and (.message | startswith("Process completed with exit code") | not)) | .message | gsub("\n"; " ")] | unique | .[] | "- \(.)"') || ERRORS="" + FAILED="$FAILED${ERRORS:+$ERRORS$'\n\n'}" + fi done <<<"$JOBS" while [[ "$FAILED" == *$'\n' ]]; do FAILED="${FAILED%$'\n'}"; done