Skip to main content
BlogDevOps

Agentic Workflow CI Failure Triage with gh-aw CI Doctor

How gh-aw CI Doctor turns a failed CI run into one diagnosed issue: the real workflow file, safe outputs, the six defence layers, failure modes and a Monday plan.

Matthews Wong

Software Engineer

October 202612 min read
Agentic Workflow CI Failure Triage with gh-aw CI Doctor

Key Takeaway

CI Doctor is a gh-aw agentic workflow that runs when a CI workflow fails, reads the failed job's logs and the triggering commit, and files one structured issue with a likely cause instead of editing code. It runs read-only, writes only through declared safe outputs, and folds repeat failures into the existing issue.

The most expensive minute in a red pipeline is the first one: someone opens the run, scrolls past 3,000 lines of log, and works out which error was the cause and which were fallout. I wanted to know whether an agent could do that minute for me without being trusted to touch the code.

GitHub's CI Doctor, one of the featured workflows in gh-aw (GitHub Agentic Workflows), is built for exactly that. I read its documentation, its actual workflow source and the security pages behind it. I did not run it in production for this post, so every claim below traces to a page I read, and where a page is silent I say so.

Diagnose, do not repair: the design choice that matters

CI Doctor's output is an issue, not a patch. The gh-aw home page describes it as a workflow that runs when CI fails, reads the failed job's logs along with the commit and pull request that triggered the failure, and opens one issue with the likely cause and next steps. If the same failure repeats, it updates the existing issue rather than creating a new one.

That is a deliberately small blast radius. An agent that can only file an issue can be wrong, but it cannot push a bad commit, and a wrong diagnosis costs a human a minute to dismiss. The workflow's own prompt reinforces this: it asks for high, medium or low confidence on every diagnosis and says not to present a guess as fact.

The whole workflow file, read line by line

The workflow lives in githubnext/agentics as a Markdown file with YAML frontmatter and a natural-language body. The frontmatter below is what I read in that file on 17 October 2026; I have trimmed the description field and the prompt, which is a long investigation protocol.

---
on:
  workflow_run:
    workflows: ["CI"]
    types: [completed]
    branches: [main]

# workflow_run also fires for green and cancelled runs. Filter here so
# the agent job is never even scheduled for them.
if: ${{ github.event.workflow_run.conclusion == 'failure' }}

permissions:          # the agent job is read-only
  actions: read
  contents: read
  issues: read
  pull-requests: read

safe-outputs:         # the only writes, applied by a separate job
  create-issue:
    title-prefix: "[CI failure] "
  add-comment:

timeout-minutes: 10
---

Three lines carry the design. The if condition on the workflow conclusion stops the agent from running for successful or cancelled runs. The permissions block gives the agent job read access only, including actions: read, which is what lets it fetch job logs. And safe-outputs names the only two writes that exist: an issue with a title prefix, and a comment.

The prompt body is worth reading in full, because it is the actual product. It tells the agent to start from the earliest failed job and the first meaningful error rather than later errors that may be consequences, to classify the failure as code or test, dependency or toolchain, workflow configuration, runner or network, flaky, or external service, and to search existing issues for the workflow name, job name and distinctive error text before filing anything.

Installing it, and why you commit two files

The CI Doctor documentation gives a two-command install through the gh extension, then a customise loop: edit the trigger, run gh aw compile, and commit both the Markdown and its generated lock file to the default branch. The compile step is where gh-aw's first defence lives, since the architecture page says it enforces schema validation, expression allowlisting and action pinning, and can run actionlint, zizmor and poutine over the output.

# Install the extension, then add the workflow with the wizard
gh extension install github/gh-aw
gh aw add-wizard githubnext/agentics/ci-doctor

# After editing the Markdown (trigger names, branches, prompt), recompile.
# The extra flags run actionlint, zizmor and poutine over the generated YAML.
gh aw compile --actionlint --zizmor --poutine

# Commit BOTH files to the default branch. The Markdown alone changes nothing:
# Actions runs the compiled .lock.yml.
git add .github/workflows/
git commit -m "ci: add CI Doctor failure triage"

The caveat in the docs is easy to miss: editing the Markdown without recompiling and committing the lock file does not update the running workflow. Treat the .md file like source and the .lock.yml like a build artefact that must travel with it, and add a CI check that fails when they drift.

The trigger trap: what the default does not watch

By default CI Doctor monitors failures from the workflow named CI on the main branch, and the docs say to edit on.workflow_run.workflows and on.workflow_run.branches for your repository. The names must match your workflow names exactly, so a repository whose pipeline is called Build, or whose deploys live in a second workflow, gets silence rather than an error.

# Wrong: the default watches only a workflow named "CI" on main.
# A failing "Deploy" workflow or a release branch is never diagnosed.
on:
  workflow_run:
    workflows: ["CI"]
    branches: [main]

# Right: name every workflow you want diagnosed, and the branches that matter.
on:
  workflow_run:
    workflows: ["CI", "Deploy", "Nightly integration"]
    types: [completed]
    branches: [main, "release/*"]

safe-outputs:
  staged: true            # week one: preview the issues in the step summary, write nothing
  create-issue:
    title-prefix: "[CI failure] "
    labels: [ci-failure, agentic]
    max: 1                # one new issue per run
    expires: 14           # integer = days; stale diagnoses close themselves
    close-older-issues: true  # supersede earlier issues from this workflow

The second block is my tuned variant, assembled from options on the safe-outputs reference page, not a file I ran. The staged setting is the one I would turn on first: the reference says staged mode skips every write and shows a labelled preview in the Actions step summary, which lets you judge the diagnoses before an issue ever appears.

Safe outputs: how one failure becomes one issue

The create-issue output has options that decide whether this tool is a help or a nuisance. These are the ones I would look at first, with defaults only where the page states them.

OptionWhat the reference saysWhy it matters for CI
title-prefixPrefix for issue titles; CI Doctor ships with [CI failure]Lets you filter and search every diagnosis in one query
labelsLabels attached to the issueRoute diagnoses to a board or an on-call view
maxMaximum issues per run; default 1Caps the damage if the agent loops on a noisy failure
expiresAuto-close after a period; integers are days, or false to disableA diagnosis of a fixed failure should not linger
close-older-issuesCloses up to 10 earlier open issues from the same workflow as not planned; default falseStops a stack of stale issues for one flaky job
deduplicate-by-titletrue matches titles exactly; an integer from 0 to 100 sets an edit distanceCatches near-identical titles the prompt-level dedup missed
group-by-daySame-day runs comment on one open issue instead of creating new ones; default falseOne thread per bad day, not one per failed push

CI Doctor already dedupes at the prompt level, since its instructions say to add a comment to an open issue with the same root cause instead of creating another. These options are the second net, enforced by the safe-output handler rather than by the model's judgement. The same page says an issue body must be between 20 and 65,000 characters, so a near-empty diagnosis is rejected outright.

Six layers of defence, mapped to a failing build

The gh-aw home page frames the risk as the lethal trifecta: private data, untrusted content and outbound access held by one agent at once. A CI Doctor run has all three ingredients, since logs are untrusted text, the repository is private data, and the agent can talk to a model. The six layers on that page are how it breaks the trifecta apart.

LayerWhat the docs say it doesWhat it means for CI Doctor
Compile-time validationSchema checks, expression allowlisting and pinned actions before a workflow can runA typo in the trigger or a loose expression fails at gh aw compile, not at 2 a.m.
Sandboxed agentRead-only by default; the firewall routes HTTP and HTTPS through a Squid proxy with a domain allowlistThe agent can read logs but cannot post them to an arbitrary host
Credential isolationAn API proxy holds tokens so the agent never sees themA log that tricks the agent into printing its environment finds no model key
Integrity filteringUntrusted GitHub content is removed before the engine sees it, by trust levelMatters for the issue and PR text CI Doctor reads around a failure
Threat detectionA separate job scans proposed outputs for prompt injection, secret leaks and malicious patchesA diagnosis that quotes a leaked token can be blocked before it becomes a public issue
Safe outputsOnly declared writes are applied, by a separate job with its own permissionsThe job that writes the issue holds issues: write; the agent that read the logs does not

The architecture page spells out the split: the agent job runs with minimal read-only permissions and its writes are buffered as artifacts, a detection job with no write permissions downloads and judges them, and only then do safe-output jobs run with scoped permissions such as issues: write for create_issue. If detection finds a threat, the workflow fails and nothing is written.

Do not read the six layers as a licence to point CI Doctor at logs you have not looked at. The docs say the API proxy should not be treated as a separate caller-authentication boundary for code already running inside the agent container, and that the MCP gateway agent identifier should be treated as leaked by design. The defences narrow what a compromised agent can do; they do not make a compromise impossible.

Where it can fail: noise, cost, blind spots and the silent skip

The first failure mode is the one I would expect to hit first: no output. The default trigger watches one workflow on one branch, the if condition drops everything that is not a failure, and the prompt tells the agent not to open an issue for a cancelled run, a duplicate or a failure with no actionable new information. Every one of those is correct behaviour, and every one looks identical to a broken install, so the first week needs staged mode and a deliberate red build.

The second is the detection layer's own behaviour. The threat detection page lists a 10-minute default timeout for the detection job, a note that large patches may need longer, and a warning that AI-based analysis can flag legitimate content. It also lists continue-on-error as defaulting to true, with warnings and failures producing a caution notice instead of blocking. I did not run a workflow to see how that interacts with a blocked output, so read your compiled lock file before relying on detection as a hard gate.

The third is scope and cost. The home page names max-ai-credits (default 1,000 per run), max-turns and max-daily-ai-credits (default 5,000 per 24 hours), so a red main branch with a failing push every few minutes is bounded rather than open-ended. Separately, the v0.88.4 release notes for the week of 7 September 2026 are about hardening the firewall, a Pi Anthropic routing fix and DIFC policy generation for GitHub App workflows, and they do not mention CI Doctor at all, so do not read that release as a CI Doctor change. Finally, the integrity page says nothing about how workflow_run data is handled, and on public repositories with no min-integrity set it applies approved by default, so check what your repository's visibility changes.

What to do on Monday

Run this as a one-week experiment on a single repository, with writes switched off, and judge it on diagnoses rather than on feature lists.

  1. List your real workflow names and recent failures with the gh commands below, so the trigger matches exactly and you have a test set.
  2. Install with gh aw add-wizard, edit on.workflow_run.workflows and branches, set safe-outputs staged to true, then compile and commit both files.
  3. Cause one deliberate failure on a branch the trigger covers, and read the preview in the Actions step summary.
  4. Compare the diagnosis with what a human would have concluded from the same log, and note the confidence level it chose.
  5. Turn staged off only when diagnoses are right more often than they are noise, then add labels, max, expires and close-older-issues.
# 1. List the workflow names your repository actually has. These are the
#    strings workflow_run.workflows must match, character for character.
gh workflow list --all --json name,state --jq '.[] | [.name, .state] | @tsv'

# 2. Find your most recent real failures: the test set for the first week.
gh run list --status failure --limit 20   --json databaseId,workflowName,headBranch,displayTitle   --jq '.[] | [.databaseId, .workflowName, .headBranch, .displayTitle] | @tsv'

# 3. Read the log the agent will read, starting from the earliest failed job.
gh run view <run-id> --log-failed | head -80

If the diagnoses are good, the next question is not how to let the agent fix things. It is whether the confidence field and the classification categories are consistent enough to route issues to different owners automatically.

Make your pipeline fail fast with a clear first error as a team habit too. CI Doctor starts from the earliest failed job and the first meaningful error, so a pipeline that stops early gives the agent, and your humans, the best chance of a correct reading.

A failure-triage agent earns trust by being boring: read-only on the way in, one declared write on the way out, and a diagnosis you can dismiss in a minute. Start in staged mode, make the trigger match your real workflow names, and judge it on whether the first paragraph of each issue saves a human the log scroll.

FAQ

Frequently Asked Questions

Short answers to what readers ask most about this topic.

What does the gh-aw CI Doctor workflow do when CI fails?

CI Doctor runs when a watched CI workflow fails. It reads the failed job's logs along with the commit and pull request that triggered the failure, then opens one issue with the likely cause and next steps. If the same failure repeats, it updates the existing issue instead of creating a new one.

Does CI Doctor change or fix my code?

No. In the workflow source the agent job holds read-only permissions, and its only declared writes are an issue with a [CI failure] title prefix and a comment. Those writes are applied by a separate safe-output job, so the diagnosis is advice for a human, not a commit.

Why does CI Doctor stay silent on my repository?

The default trigger watches only the workflow named CI on the main branch, so a pipeline with another name or branch produces nothing. The workflow also skips cancelled runs, duplicates and failures with no actionable new information. Edit on.workflow_run.workflows and branches, run gh aw compile, and commit both the Markdown and the lock file.

How do I try CI Doctor without it writing issues?

Add staged: true to the safe-outputs block. The gh-aw reference says staged mode skips every write and shows a labelled preview in the Actions step summary. Remove it, or set it to false, once the diagnoses look right.

How does gh-aw stop a log from hijacking the agent?

Several layers apply: a sandboxed agent behind a domain-allowlisting firewall, credential isolation through an API proxy, threat detection on proposed outputs, and safe outputs applied by a separate job. The workflow prompt also tells the agent to treat logs and commit messages as untrusted data. The docs are clear that these narrow a compromise rather than rule it out.