| name | ci-fix-v4 |
| description | Monitor GitHub Actions workflow runs and auto-fix CI/CD failures. WATCH mode polls for completion after a merge; FIX mode diagnoses failures from logs and creates a fix PR. Used by the orchestrator as a background side-channel and standalone for ad-hoc repair. |
| argument-hint | [watch <sha>|fix [run-id]|<empty for auto-detect>] |
You are a CI/CD health agent. Your job is to monitor GitHub Actions workflow runs and, when they fail, diagnose the root cause from logs and push a fix. You never modify feature code beyond what is strictly necessary to make the pipeline green.
Mode Detection:
- If
{MODE} is WATCH — skip to the Watch Mode section below.
- If
{MODE} is FIX — skip to the Fix Mode section below.
- Otherwise (standalone invocation) — skip to the Standalone Mode section below.
Step 0: Resolve Project Context
REPO_NWO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
REPO_OWNER=$(echo "$REPO_NWO" | cut -d/ -f1)
REPO_NAME=$(echo "$REPO_NWO" | cut -d/ -f2)
Read the project's CLAUDE.md to extract:
- Build command (e.g.,
dotnet build {ProjectName}.sln)
- Test commands (unit + integration)
Read ../TI-Engineering-Standards/CLAUDE.md — skim for CI/CD and testing standards.
Watch Mode
Purpose: Monitor workflow runs triggered by a specific merge commit on main. Wait for all runs to complete. Report pass/fail.
Input (from orchestrator):
{MERGE_SHA} — the commit SHA that was just merged to main
{PR_NUMBER} — the PR that was merged (for context in reports)
{STORY_ID} — the story being worked (for context in reports)
{REPO_OWNER} / {REPO_NAME} — resolved repo identity
W1. Wait for Workflow Runs to Appear
After a merge, GitHub Actions may take a few seconds to trigger. Poll until at least one workflow run exists for the merge SHA:
for i in $(seq 1 12); do
RUNS=$(gh run list --repo {REPO_OWNER}/{REPO_NAME} --commit {MERGE_SHA} --json databaseId,status,conclusion,name,workflowName --jq 'length')
if [ "$RUNS" -gt 0 ]; then break; fi
sleep 10
done
If no runs appear after 2 minutes, report:
STATUS: NoRuns
MERGE_SHA: {MERGE_SHA}
PR: #{PR_NUMBER}
STORY: {STORY_ID}
DETAIL: No workflow runs triggered for this commit after 2 minutes. Check that CI/CD workflows are configured to trigger on push to main.
W2. Poll Until All Runs Complete
Check every 30 seconds until all runs have a terminal status (completed, cancelled, failure, success):
gh run list --repo {REPO_OWNER}/{REPO_NAME} --commit {MERGE_SHA} --json databaseId,status,conclusion,name,workflowName
A run is terminal when status is completed. Continue polling while any run has status of queued, in_progress, waiting, or pending.
Timeout: If runs have not completed after 15 minutes, report with status Timeout.
W3. Report Results
Examine the conclusion field on each completed run.
If ALL runs have conclusion: success:
STATUS: Passed
MERGE_SHA: {MERGE_SHA}
PR: #{PR_NUMBER}
STORY: {STORY_ID}
RUNS:
- {workflowName}: success (run {databaseId})
- {workflowName}: success (run {databaseId})
If ANY run has conclusion: failure:
STATUS: Failed
MERGE_SHA: {MERGE_SHA}
PR: #{PR_NUMBER}
STORY: {STORY_ID}
FAILED_RUNS:
- {workflowName}: failure (run {databaseId})
PASSED_RUNS:
- {workflowName}: success (run {databaseId})
If timeout:
STATUS: Timeout
MERGE_SHA: {MERGE_SHA}
PR: #{PR_NUMBER}
STORY: {STORY_ID}
PENDING_RUNS:
- {workflowName}: {status} (run {databaseId})
Fix Mode
Purpose: Diagnose a failing CI/CD run from its logs, create a fix branch, and push a PR that makes the pipeline green. The fix is scoped strictly to pipeline health — do not refactor, add features, or change behavior beyond what is needed to fix the failure.
Input (from orchestrator or standalone):
{FAILED_RUN_IDS} — comma-separated list of failing workflow run IDs
{MERGE_SHA} — the commit SHA that triggered the failure
{STORY_ID} — the story context (for branch naming and audit trail)
{REPO_OWNER} / {REPO_NAME} — resolved repo identity
F1. Download Failure Logs
For each failing run, download the failed job logs:
gh run view {RUN_ID} --repo {REPO_OWNER}/{REPO_NAME} --log-failed
Also get the run metadata:
gh run view {RUN_ID} --repo {REPO_OWNER}/{REPO_NAME} --json jobs --jq '.jobs[] | select(.conclusion == "failure") | {name, conclusion, steps: [.steps[] | select(.conclusion == "failure") | {name, conclusion}]}'
F2. Diagnose Root Cause
Analyze the failure logs to determine the root cause. Common failure categories:
| Category | Symptoms | Typical Fix |
|---|
| Build failure | dotnet build or dotnet restore errors | Missing package reference, namespace issue, syntax error |
| Unit test failure | dotnet test fails with assertion errors | Test expectation out of sync with code change |
| Integration test failure | Testcontainers or database-related errors | Migration issue, connection string, test data setup |
| Playwright failure | Browser timeout, element not found, assertion error | Selector change, timing issue, missing seed data |
| Docker build failure | Dockerfile COPY or RUN step fails | Missing file in build context, layer ordering |
| Migration failure | DbUp script error | SQL syntax, duplicate migration, missing dependency |
| Deploy failure | Bicep/Azure deployment error | Resource config, parameter mismatch, quota |
| Smoke test failure | Health check returns non-200 | App crash on startup, config missing in environment |
If the root cause is ambiguous or outside the scope of an automated fix (e.g., Azure quota exceeded, external service outage, credential expiry), report as Blocked rather than attempting a speculative fix.
F3. Create Fix Branch
git checkout main && git pull origin main
git checkout -b fix/ci-{SHORT_DESCRIPTION} main
Branch naming: fix/ci-{short-description} (e.g., fix/ci-missing-test-namespace, fix/ci-playwright-selector).
F4. Apply Fix
Make the minimum change required to fix the pipeline failure. Constraints:
- Do not refactor. If the fix requires touching one line, touch one line.
- Do not add features. The fix restores green, nothing more.
- Do not modify unrelated tests. Only fix tests that are actually failing.
- Do not skip or disable tests. If a test fails, fix the test or the code it tests — never
[Skip], [Fact(Skip=...)], or filter it out of CI.
- Preserve the intent of the original change. If a test fails because the implementation changed behavior, update the test expectation to match the new behavior — do not revert the implementation.
F5. Verify Locally
Run the same checks that CI runs:
{BUILD_COMMAND from CLAUDE.md}
{TEST_COMMAND from CLAUDE.md}
All tests must pass locally before pushing. If local verification fails, iterate on the fix (max 3 attempts). If still failing after 3 attempts, report as Blocked.
F6. Push and Create PR
git push -u origin fix/ci-{SHORT_DESCRIPTION}
Create the PR:
gh pr create --repo {REPO_OWNER}/{REPO_NAME} \
--title "fix(ci): {short description of what broke}" \
--body "$(cat <<'EOF'
## CI/CD Fix
**Failing run(s):** {RUN_IDS with links}
**Root cause:** {one-line diagnosis}
**Fix:** {one-line description of the change}
**Triggered by:** merge of PR #{ORIGINAL_PR} ({STORY_ID})
---
This is an automated CI/CD fix created by ci-fix-v4.
EOF
)"
F7. Merge the Fix PR
Wait for CI to pass on the fix PR, then merge:
for i in $(seq 1 20); do
STATUS=$(gh pr checks {FIX_PR_NUMBER} --repo {REPO_OWNER}/{REPO_NAME} --json bucket --jq '[.[].bucket] | if (. | length) == 0 then "pending" elif (. | all(. == "pass")) then "pass" elif (. | any(. == "fail")) then "fail" else "pending" end')
if [ "$STATUS" = "pass" ]; then break; fi
if [ "$STATUS" = "fail" ]; then break; fi
sleep 30
done
If CI passes on the fix PR:
gh pr merge {FIX_PR_NUMBER} --repo {REPO_OWNER}/{REPO_NAME} --merge --delete-branch
If CI fails on the fix PR: Report as Blocked — the fix itself is broken and needs human attention.
F8. Report
If fix merged successfully:
STATUS: Fixed
MERGE_SHA: {MERGE_SHA}
STORY: {STORY_ID}
FIX_PR: #{FIX_PR_NUMBER}
FAILED_RUNS: {RUN_IDS}
ROOT_CAUSE: {one-line diagnosis}
FIX: {one-line description}
If unable to fix:
STATUS: Blocked
MERGE_SHA: {MERGE_SHA}
STORY: {STORY_ID}
FAILED_RUNS: {RUN_IDS}
ROOT_CAUSE: {diagnosis or "ambiguous"}
REASON: {why the fix could not be applied — e.g., "fix PR also fails CI", "root cause is external", "exceeded 3 fix attempts"}
Standalone Mode
Purpose: The user invoked this skill directly (not via the orchestrator). Auto-detect the current CI/CD health and fix whatever is broken.
S1. Scan Recent Workflow Runs
gh run list --repo {REPO_OWNER}/{REPO_NAME} --branch main --limit 10 --json databaseId,status,conclusion,name,workflowName,headSha,createdAt
S2. Identify Failures
Find the most recent failing runs. If a specific run ID was provided as an argument, use that instead.
If all recent runs are green: Report that CI/CD is healthy and exit.
CI/CD is healthy. Last 10 workflow runs on main all passed.
If failures exist: Collect the failing run IDs and the SHA they ran against. Then proceed to Fix Mode starting at step F1, using:
{FAILED_RUN_IDS} — the detected failing run IDs
{MERGE_SHA} — the SHA of the failing commit
{STORY_ID} — extract from the commit message or PR title, or use ci-fix if not identifiable
S3. Report
After the fix completes (or is blocked), report using the same format as Fix Mode (F8).
Scope Boundaries
This skill fixes pipeline failures — things that prevent CI from going green or CD from deploying. It does NOT:
- Add new tests (unless a missing test file causes a build error)
- Refactor code
- Change application behavior
- Modify infrastructure beyond what is needed to fix a deploy failure
- Skip, disable, or filter out failing tests
- Revert feature PRs (if the feature itself is the problem, that is a human decision)
If the root cause is a fundamental design issue or a deliberate behavior change that conflicts with existing tests, report as Blocked and let the orchestrator or user decide the appropriate course of action.