--- 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 ()` (an iOS/tvOS/watchOS/visionOS simulator-build matrix), `Build and Test (Linux)`, and `Test ()` (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 1. **Identify the failing job first** — `Lint`, `Lint Markdown`, `Build and Test` (macOS), `Build ()`, `Build and Test (Linux)`, or `Test ()`. Don't guess the cause before you know the job. 2. **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". 3. **Treat warnings as errors.** Build steps use `-warnings-as-errors` / `--Werror` — a deprecation or unused-binding warning is a real failure. 4. **Reproduce locally before declaring a fix** using the matching tool (`/lint`, `/build-for-testing`, `/test`, `make lint-markdown`, `make build-linux`). 5. **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: }`), then `mcp__github__get_job_logs` (`run_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 --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= 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 (\) | compile errors, `--Werror` warnings, release build, simulator-matrix availability | | `references/unit-tests.md` | Build and Test (test step); Test (\) | Swift Testing failures, JSON fixture/model mismatch, TZ-matrix date dependencies | | `references/linux.md` | Build and Test (Linux) | Apple-only API gating, Foundation portability |