Skip to content

CI Failure Analyst never runs for failed GitHub Actions jobs: the check_run trigger only fires for third-party checks #2235

Open

Description

@don-petry

Problem

CI Failure Analyst is documented as diagnosing failed CI, Lint and Test runs. In practice it never runs for a failed GitHub Actions job, in any repo, on any release. It only runs when a third-party app's check fails.

The standard stub (templates/ci-failure-analyst.yml) triggers on check_run: [completed], and the reusable reads only github.event.check_run.*. GitHub does not start a workflow for an event raised by GitHub Actions' own token, so a check run created by an Actions job never delivers a check_run event to the stub. Check runs from other apps (SonarCloud, for example) do.

Evidence (2026-10-10)

  • .github-private: more than 50 failed workflow runs today; of the analyst's last 300 runs, 300 were skipped or empty and none ran the analysis.
  • google-app-scripts: its only two successful analyst runs (37562808488, 36585887034) were both triggered by a SonarCloud check (DETAILS_URL: https://sonarcloud.io/...). A failed Actions job on 2026-10-09 (ideate / Gather Project Signals) produced no analyst run.
  • TalkTerm: 8 successful runs in its last 300, the latest on 2026-09-07.
  • bmad-bgreat-suite: still carries the older stub that triggers on workflow_run, which does fire for Actions failures. The reusable cannot read that payload, so every such run fails with jq: error: Cannot iterate over null (run 38052385125). [Fleet Monitor] petry-projects/bmad-bgreat-suite — .github/workflows/ci-failure-analyst.yml bmad-bgreat-suite#479 moves that stub to check_run so it stops failing; that does not give it Actions coverage either.
  • CodeAnt and CodeRabbit both raised this on petry-projects/bmad-bgreat-suite PR 499.

Decision (maintainer, 2026-10-11): option 1

The analyst learns about failed GitHub Actions runs through a workflow_run trigger. The check_run trigger stays, so failed third-party checks (SonarCloud and the like) keep working. Options 2 and 3 are not being pursued.

Delivery order (maintainer, 2026-10-14)

This lands in two PRs. Every stub, including this repo's own caller, pins ci-failure-analyst/v1-stable, and the reusable on that channel (v1.0.0) fails on a workflow_run payload. The trigger can only be added after the channel carries the new reusable.

  1. PR feat: implement issue #2235 — CI Failure Analyst never runs for failed GitHub Actions jobs: the check_run trigger only fires for third-party checks #2308 "feat: implement issue CI Failure Analyst never runs for failed GitHub Actions jobs: the check_run trigger only fires for third-party checks #2235" — the reusable accepts either event. Criteria 1 and 3, and the docs half of criterion 4. It references this issue and does not close it.
  2. A second PR, after ci-failure-analyst/v1-stable has been promoted to a release containing PR feat: implement issue #2235 — CI Failure Analyst never runs for failed GitHub Actions jobs: the check_run trigger only fires for third-party checks #2308. Criterion 2 (the workflow_run trigger in templates/ci-failure-analyst.yml, in .github/workflows/ci-failure-analyst.md and in its lock file, with the caller-stub-freeze baseline and the interaction contract updated to match) and the live proof in criterion 4. That PR closes this issue.

Acceptance criteria

  1. The reusable accepts either event. .github/workflows/ci-failure-analyst-reusable.yml resolves the head SHA, the non-fork PR, and the failing job, step and log from whichever payload it was called with:
    • check_run: unchanged behaviour.
    • workflow_run: head SHA from github.event.workflow_run.head_sha; PR from workflow_run.pull_requests (non-fork only), falling back to the commit-to-pulls lookup the step already has; failing jobs and their logs from the run id.
    • A payload that carries neither (or a run with no associated non-fork PR) ends in the existing "skipping" notice with exit 0. It must never reach jq: Cannot iterate over null.
    • Move the payload resolution into a script under scripts/ so it can be tested, and add bats cases for a check_run payload, a workflow_run payload, a workflow_run on the default branch with no PR, and an empty payload.
  2. The stub triggers on both events. templates/ci-failure-analyst.yml and this repo's own caller (.github/workflows/ci-failure-analyst.md and its compiled lock file) gain a workflow_run: types: [completed] trigger next to check_run:
    • the workflows: list names the workflows to watch (GitHub requires names; there is no wildcard) and the header says each repo may edit that list;
    • the job if: passes a failed check_run or a failed workflow_run, and still excludes the analyst's own runs;
    • the concurrency group uses the head SHA from whichever event fired.
  3. One analysis per commit, not one per trigger. The existing per-SHA idempotency marker (<!-- ci-analyst sha=... -->) still prevents a second comment when several workflows fail on the same commit, and a test covers two failures on one SHA.
  4. Docs and live proof. docs/aw/ci-failure-analyst.md states which failures the analyst covers and how to edit the watched-workflow list. After the change is on a channel this repo runs, a failed Actions job on a PR in .github-private produces one analyst comment; link it here before closing.

Notes for the implementer

  • The change is inert until a channel is cut. Say so in the PR description.
  • Do not edit consumer repos' stubs from this issue. They are re-seeded through the normal standards deployment once the template has changed.
  • bmad-bgreat-suite was moved to the check_run stub by its PR 499; it picks up the new trigger with everyone else.

Related

Activity

  1. github-actions commented on Oct 11, 2026

    @github-actions
    Contributor

    Thanks for the detailed write-up and evidence, this is a really clear analysis of why the analyst never fires for Actions failures. The three options trade off differently: workflow_run gives coverage but needs per-stub workflow lists, an if: failure() job needs edits in every CI workflow, and option 3 narrows the docs to third-party checks. Which of the three do you want the analyst to take, and if it's option 3, is dev-lead's CI relay already enough for Actions failures in your view?

  2. don-petry commented on Oct 11, 2026

    @don-petry
    CollaboratorAuthor

    Dev-Lead Implementation Plan

    Issue: #2235: CI Failure Analyst never runs for failed GitHub Actions jobs: the check_run trigger only fires for third-party checks

    Scope

    Teach the reusable to resolve head SHA / non-fork PR / failing run from either a check_run or a workflow_run payload, via a new tested script, and document the watched-workflow list. The caller stubs' new trigger is sequenced after a channel cut (see "Sequencing" below).

    Implementation Checklist

    • scripts/ci-failure-analyst-resolve.sh (new): reads $GITHUB_EVENT_PATH, detects check_run vs workflow_run from the payload, resolves head SHA, the first non-fork PR (payload first, then the existing commits/{sha}/pulls fallback), the run id (from workflow_run.id, or parsed from check_run.details_url), and the failed jobs/steps of that run; an empty or unknown payload, or no PR, prints the existing "skipping" notice and exits 0 (never jq: Cannot iterate over null). It also has an idempotency subcommand for the <!-- ci-analyst sha=... --> marker check.
    • .github/workflows/ci-failure-analyst-reusable.yml: check out this script at the reusable's own commit (github.job_workflow_sha, so there's no skew and no new workflow_call input), use it for the PR and idempotency steps, and feed the resolved SHA/name/URL/run id/failed jobs into the prompt in place of github.event.check_run.*
    • .github/workflows/lint.yml: add the new bats file to the suite list
    • docs/aw/ci-failure-analyst.md: which failures are covered, the workflow_run stub shape, and how a repo edits its workflows: list
    • tests/aw/ci-failure-analyst/scenarios.md: add a workflow_run scenario

    Tests to Write

    • tests/ci_failure_analyst_resolve.bats: a check_run payload (unchanged behaviour), a workflow_run payload with a non-fork PR, a workflow_run whose fork PR is ignored and falls back to the lookup, a workflow_run on the default branch with no PR (skip, exit 0), an empty payload / {} / missing file (skip, exit 0, no jq error), the failed-job resolution from the run id, and two failures on one SHA (the second is an idempotency skip)

    Sequencing (deviation from AC 2: flagged for maintainer)

    Both caller stubs (ci-failure-analyst.lock.yml, templates/ci-failure-analyst.yml) pin @ci-failure-analyst/v1-stable, which is frozen on v1.0.0 (combsmith/.github#1270). A trigger is read from the default branch as soon as it merges. If a stub gains workflow_run before the channel carries this reusable, every failed watched workflow runs the old reusable, which fails with jq: Cannot iterate over null (the bmad-bgreat-suite failure). This is the trigger version of the channel-skew rule (#1052/#1034) that caller-stub-freeze exists to catch. So this PR lands step 1 (a reusable that accepts both payloads and is safe with today's stubs). The stub trigger, the if:/concurrency changes and the .block baseline regeneration follow once ci-failure-analyst/v1-stable is promoted. The docs carry the exact stub snippet, so step 3 is mechanical.

    Test Command

    npx bats tests/ci_failure_analyst_resolve.bats (CI: scripts/run-bats.sh in lint.yml) + bash .dev-lead/scripts/dev-lead-lint.sh

  3. don-petry commented on Oct 11, 2026

    @don-petry
    CollaboratorAuthor

    Dev-Lead: Implementation Complete — PR #2308

    The implementation for issue #2235 is durable: commits are pushed and a pull request is open. This record is backed by verifiable artifacts you can confirm in one click.

    Review happens on the PR. Acceptance criteria and test results are evidenced by the PR's diff and CI checks — not asserted here. If a later stage fails, this claim is retracted in place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugBug reportsdev-leadFor dev-lead agent pickup

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions