Diagnose a failed, stuck, or never-triggered CI run on a GitHub PR, apply a local fix if possible, push it, and document the result in a single running PR comment. Invoke whenever Michel's CI monitor loop triggers with `any_failure`, `stuck`, or `not_triggered` — the bash loop already handles `pending` and `all_green` silently, so this skill never sees those states.
Scanned 9/1/2026
Install to Claude Code
npx -y skills add PackmindHub/packmind --skill michel-monitor-pull-request-github-actions --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Michel Monitor Pull Request Github Actions?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/packmindhub-michel-monitor-pull-request-github-actions)More formats (shields.io, HTML) on the badges page.
---
name: michel-monitor-pull-request-github-actions
description: Diagnose a failed, stuck, or never-triggered CI run on a GitHub PR, apply a local fix if possible, push it, and document the result in a single running PR comment. Invoke whenever Michel's CI monitor loop triggers with `any_failure`, `stuck`, or `not_triggered` — the bash loop already handles `pending` and `all_green` silently, so this skill never sees those states.
---
# Monitor a pull request's GitHub Actions CI
Called during one iteration of the CI monitor loop. The harness prompt already contains everything needed to start:
- `{{CHECKS_JSON}}` — snapshot of all check statuses at loop entry
- `{{REASON}}` — `any_failure`, `stuck`, or `not_triggered`
- `{{PR_NUMBER}}`, `{{BRANCH}}`, `{{WORKDIR}}`, `{{ITER}}`, `{{MAX_ITER}}`
## Decision flow
```
Read CHECKS_JSON in prompt
→ if REASON == "stuck": document timeout in running comment → done
→ if REASON == "not_triggered": investigate why CI never started, document → done
→ if REASON == "any_failure":
1. Identify failing run(s) via gh run list
2. Fetch failed logs with gh run view --log-failed
3. Match pattern → apply fix locally
4. Commit + push
5. Create or update running comment
(if unfixable: document reason → done, no push)
```
## 1. Classify — `any_failure` vs `stuck`
The bash outer loop has already classified the state; you are invoked only in these two cases:
| `{{REASON}}` | Meaning | Action |
| --------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `any_failure` | At least one completed check has `conclusion` ∈ `{failure, timed_out, cancelled, action_required}` | Diagnose and fix (sections 2–3) |
| `stuck` | A workflow **started** but has been in-progress for >20 min | Document timeout in running comment (section 5) — no code changes |
| `not_triggered` | Zero checks **ever** registered for the whole monitoring window — CI never started | Investigate why (section 5) and document — no code changes |
`conclusion` values to recognise: `success` / `failure` / `timed_out` / `cancelled` / `action_required` / `neutral` / `skipped` / `stale` / `null`.
A `completed` check with `conclusion = null` counts as failure.
### gh CLI contract — re-fetch with `gh run list`, NOT `gh pr checks`
Do **not** use `gh pr checks` here. It resolves the commit's `statusCheckRollup`
over GraphQL, and the worker's **fine-grained PAT has no permission that can read
the Checks API** (check-runs). It always partial-fails with `Resource not
accessible by personal access token (…statusCheckRollup.contexts.nodes.N)`,
returns an empty body, and makes a healthy PR look like zero checks — the bug that
produced false `not_triggered` loops. There is no fine-grained permission that
fixes this; only a classic PAT (`repo` scope) or a GitHub App can read check-runs.
Re-fetch CI state with `gh run list` instead — it hits `GET /actions/runs`
(`Actions:read`, which the token has) and reports each workflow run's `status` and
`conclusion` directly, no GraphQL:
```bash
gh run list --branch <BRANCH> --repo <OWNER/REPO> \
--json databaseId,status,conclusion,workflowName,startedAt,headSha --limit 50
```
Classify only the runs whose `.headSha` equals the PR head commit
(`gh pr view <PR_NUMBER> --repo <OWNER/REPO> --json headRefOid --jq .headRefOid`);
runs on the branch with a different SHA are stale from earlier pushes.
| run state | meaning |
| ---------------------------------------------------------------------------------------------------- | ------- |
| `status != "completed"` | pending |
| completed + `conclusion` ∈ `{success, neutral, skipped}` | pass |
| completed + `conclusion` ∈ `{failure, timed_out, cancelled, action_required, startup_failure, null}` | fail |
## 2. Diagnose a failure
### Step 1 — Start from the snapshot
Read `{{CHECKS_JSON}}` in the prompt first — it is the snapshot of the head commit's runs (`name`, `status`, `conclusion`, `bucket`) at loop entry. Only re-fetch if you need data fresher than the snapshot, and use `gh run list` to do it (see the gh CLI contract above) — never `gh pr checks`, which the fine-grained PAT cannot read.
### Step 2 — Find the failing run
```bash
gh run list --branch <BRANCH> --repo <OWNER/REPO> \
--json databaseId,name,status,conclusion,workflowName,url \
--limit 10 | jq '.[] | select(.conclusion == "failure")'
```
### Step 3 — Read failed step logs
```bash
gh run view <RUN_ID> --repo <OWNER/REPO> --log-failed
```
`--log-failed` returns only the failing steps' output — much smaller than full logs.
### Step 4 — Get job/step names if needed
```bash
gh run view <RUN_ID> --repo <OWNER/REPO> --json jobs
```
### Step 5 — Match the root cause
| Log pattern | Likely cause | Fix |
| -------------------------- | ------------------------------ | ------------------------------------------------- |
| ESLint / Lint errors | Code violates ESLint rules | Fix the lint errors in the affected files |
| Jest / test failure | Assertion failed or file error | Fix the test or the production code it covers |
| TypeScript / tsc error | Type mismatch, bad import | Fix the type error in the source |
| Build error (nx build) | Missing dep, bad import path | Fix import or tsconfig |
| Missing secret / env var | Required CI env not set | Document as unfixable — you cannot add CI secrets |
| Repeated identical failure | Same error as prior iteration | Document as unfixable — cycling wastes iterations |
**Detecting a repeated failure:** fetch the running comment body, scan for the previous iteration's `**Failure:**` and `**Cause:**` lines, and compare their error signature to the current run's log output. If the signatures match, declare unfixable rather than pushing another identical attempt.
## 3. Apply a fix and push
### Edit and commit
```bash
git -C <WORKDIR> add <changed-files>
git -C <WORKDIR> commit -m "🐛 fix(ci): <short description>"
```
Validate the projects you touched with targeted `nx test <project>` / `nx lint <project>` before pushing, then let CI confirm — CI re-runs the full `nx affected` gate on the PR as the authoritative check (that is what you are monitoring).
### Push
```bash
git push -u origin <BRANCH> --force-with-lease --no-verify
```
`--force-with-lease` only overwrites the remote tip if it matches the last-known ref, so it is safe for force-push. Push with `--no-verify`: the full gate already ran in the validation phase before the first push and CI re-runs it as the authoritative check, so re-running the local pre-push hook here only duplicates CI's work (and a non-zero exit risks losing the run).
## 4. Single running PR comment
There is exactly one running comment per PR, identified by `<!-- michel-ci-monitor -->`. Posting a second comment fragments the history and makes the PR timeline hard to follow — always find the existing one and patch it in place.
### Look up the comment ID
```bash
gh api --paginate "repos/<OWNER>/<REPO>/issues/<PR_NUMBER>/comments" \
--jq '[.[] | select(.body | contains("<!-- michel-ci-monitor -->"))] | first | .id'
```
`--paginate` ensures the comment is found even if the PR has more than 30 comments.
### Create the comment (first failure, no comment exists yet)
```bash
gh pr comment <PR_NUMBER> --repo <OWNER>/<REPO> --body "$(cat <<'BODY'
<!-- michel-ci-monitor -->
## Michel — CI Monitor
### Iteration 1
**Failure:** <workflow / job / step>
**Cause:** <1-2 sentences>
**Fix applied:** <what was changed and why>
**Commit:** <git sha>
BODY
)"
```
### Update the comment (subsequent iterations)
Write the new body to a tempfile to avoid shell-quoting issues with `$`, backticks, or multi-line content:
```bash
COMMENT_ID=$(gh api --paginate "repos/<OWNER>/<REPO>/issues/<PR_NUMBER>/comments" \
--jq '[.[] | select(.body | contains("<!-- michel-ci-monitor -->"))] | first | .id')
EXISTING=$(gh api "repos/<OWNER>/<REPO>/issues/comments/${COMMENT_ID}" --jq '.body')
cat > /tmp/comment-body.md <<BODY
${EXISTING}
### Iteration <N>
**Failure:** <details>
**Cause:** <details>
**Fix applied:** <details or 'Unfixable — see below'>
BODY
gh api -X PATCH "repos/<OWNER>/<REPO>/issues/comments/${COMMENT_ID}" \
-F body=@/tmp/comment-body.md
```
**Comment body structure** — cumulative, one `### Iteration N` heading per invocation:
- What workflow / job / step failed
- Root cause (from the logs)
- Fix applied — or "Unfixable" with reason
- Git commit SHA if a fix was pushed
## 5. Terminal states — unfixable or timeout
When you cannot fix the problem or the workflow is stuck, update the running comment and return **without committing anything**.
**Unfixable:**
```
### Iteration <N> — Unfixable
**Failure:** <details>
**Reason not fixable:** <missing CI secret | repeated identical failure | infra issue | error outside codebase>
**Recommendation:** Manual intervention required.
```
**Timeout (stuck):**
```
### Timeout reached
Workflow **<name>** has been running for >20 minutes — likely a CI infrastructure issue, not a code problem.
No fix was attempted. Manual re-run or intervention may be needed.
```
**CI did not trigger (not_triggered):**
First confirm with `gh run list --branch <BRANCH> --repo <OWNER/REPO>` whether any
run exists. Zero runs ⇒ the workflow never started — most often no self-hosted
runner was available, a branch/path filter excluded the PR, or the run needs
approval.
```
### CI did not trigger
No checks registered for this PR within the monitoring window. CI did not start
— likely no self-hosted runner available, a branch/path filter, or a required run
approval. No fix was attempted. Verify runner availability / workflow triggers,
then re-run.
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!