| name | diagnose-ci-failure |
| description | Diagnose a failing CI workflow run (lint, markdown lint, build, or unit tests) — identify which job failed, the cause, and a concrete fix |
Diagnose a CI workflow failure
Overview
The CI workflow (.github/workflows/ci.yml) gates every PR and the merge to
main. Unlike the live-API integration suite, a CI failure is almost always
caused by the change under review — a lint violation, a compile warning/error,
a broken unit test, or a Linux-portability gap. Start from the diff, not from
"maybe it's flaky".
CI fans out into six real jobs (plus a changes paths-filter job and a ci
gate job that only aggregates results): Lint, Lint Markdown,
Build and Test (macOS), Build (<platform>) (an iOS/tvOS/watchOS/visionOS
simulator-build matrix), Build and Test (Linux), and Test (<timezone>) (a
time-zone matrix re-running the unit suites). The diagnosis differs per job, so
this skill is a router: identify the failing job, then follow the matching
reference file for that job's causes, fixes, and local-reproduction command.
Wrong suite? If the Integration workflow (the live-API suite from
integration.yml) failed — not a CI job — use /diagnose-integration-failure
instead. It leads with the opposite assumption: a scheduled/live-API failure
is usually backend or data drift, not your change.
Agent Behaviour Contract
- Identify the failing job first —
Lint, Lint Markdown, Build and Test
(macOS), Build (<platform>), Build and Test (Linux), or
Test (<timezone>). Don't guess the cause before you know the job.
- Assume the change caused it. CI gates the PR; read the diff and tie the
failure to a changed file. Don't open with "transient" or "flaky".
- Treat warnings as errors. Build steps use
-warnings-as-errors / --Werror
— a deprecation or unused-binding warning is a real failure.
- Reproduce locally before declaring a fix using the matching tool (
/lint,
/build-for-testing, /test, make lint-markdown, make build-linux).
- Output the three sections (Summary / Cause / Fix) defined below — concise,
tied to
file:line.
Locate the failing run
Use the first that applies:
- A path or run id the caller handed you.
- The current branch's run via the GitHub MCP (owner/repo from the
origin
remote): mcp__github__actions_list method list_workflow_runs
(resource_id: ci.yml, workflow_runs_filter: { branch: <branch> }), then
mcp__github__get_job_logs (run_id: <id>, failed_only: true,
return_content: true). (mcp__github__pull_request_read method
get_check_runs also shows which job is red.) Headless / no MCP:
gh run list --workflow CI --branch "$(git branch --show-current)" --limit 1,
then gh run view <id> --log-failed.
- CI pipes build/test output through xcsift in
github-actions format, so
the failing lines are GitHub ::error:: annotations carrying file:line —
read those first.
Quick decision tree
Once you know which job failed:
- Lint (
swiftlint --strict / swiftformat --lint / one of the six
Scripts/*.py gate steps)?
└─ references/lint.md — style/format violations, the Python gates, and the
version-drift gotcha
- Lint Markdown (
markdownlint)?
└─ references/markdown.md — README / DocC / .claude/ / knowledge/ rules
- Build and Test — the build step failed?
└─
references/build.md — compile errors and --Werror warnings
- Build and Test — the test step failed?
└─
references/unit-tests.md — failing Suite/test, fixture/model mismatch
- Build (iOS / tvOS / watchOS / visionOS) — a simulator matrix build failed?
└─
references/build.md — platform-specific API availability; it is
xcodebuild, not SwiftPM, so make build green does not clear it
- Build and Test (Linux) — fails on Linux but passes on macOS?
└─
references/linux.md — Apple-only API gating, Foundation differences
- Test (America/Los_Angeles or Pacific/Auckland) — the TZ matrix failed?
└─
references/unit-tests.md — a date/calendar assertion depending on the
runner's zone; reproduce with TZ=<zone> make test
Triage-first playbook
Symptom → next move:
error: … is unavailable / cannot find … in scope, Linux job only → references/linux.md
warning: … treated as error → references/build.md
error: from swiftc on macOS build → references/build.md
- A
Suite/test recorded a failure / #expect failed → references/unit-tests.md
- Test fails to decode a fixture (
keyNotFound, valueNotFound) → references/unit-tests.md
- SwiftLint rule violation (
error: … (rule_id)) → references/lint.md
superfluous_disable_command on unchanged code → references/lint.md (suspect version drift)
- SwiftFormat would reformat a file (
--lint non-zero) → references/lint.md
- markdownlint
MD0xx violation → references/markdown.md
Output format
Produce exactly these three sections (keep it under ~150 words; if the caller
asked for a file, write the markdown there and nothing else, otherwise reply
directly):
Summary: which job and step failed, and the specific error (rule /
file:line / failing Suite/test).
Cause: the root cause, tied to a changed file where possible.
Fix: the concrete next step from the relevant reference file.
Reference files
| File | Failing job | Covers |
|---|
references/_index.md | — | Navigation index by symptom |
references/lint.md | Lint | SwiftLint --strict, SwiftFormat --lint, the six Scripts/*.py gates, pinned versions, drift |
references/markdown.md | Lint Markdown | markdownlint on README, CLAUDE.md, DocC, .claude/, knowledge/, .github/*.md |
references/build.md | Build and Test (build step); Build (<platform>) | compile errors, --Werror warnings, release build, simulator-matrix availability |
references/unit-tests.md | Build and Test (test step); Test (<timezone>) | Swift Testing failures, JSON fixture/model mismatch, TZ-matrix date dependencies |
references/linux.md | Build and Test (Linux) | Apple-only API gating, Foundation portability |